diff options
Diffstat (limited to 'browser/base/content/test')
702 files changed, 57478 insertions, 0 deletions
diff --git a/browser/base/content/test/alerts/.eslintrc.js b/browser/base/content/test/alerts/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/alerts/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/alerts/browser.ini b/browser/base/content/test/alerts/browser.ini new file mode 100644 index 000000000..07fcf5253 --- /dev/null +++ b/browser/base/content/test/alerts/browser.ini @@ -0,0 +1,12 @@ +[DEFAULT] +support-files = + head.js + file_dom_notifications.html + +[browser_notification_close.js] +[browser_notification_do_not_disturb.js] +[browser_notification_open_settings.js] +[browser_notification_remove_permission.js] +[browser_notification_permission_migration.js] +[browser_notification_replace.js] +[browser_notification_tab_switching.js] diff --git a/browser/base/content/test/alerts/browser_notification_close.js b/browser/base/content/test/alerts/browser_notification_close.js new file mode 100644 index 000000000..bbd444212 --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_close.js @@ -0,0 +1,71 @@ +"use strict"; + +const {PlacesTestUtils} = + Cu.import("resource://testing-common/PlacesTestUtils.jsm", {}); + +let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; +let oldShowFavicons; + +add_task(function* test_notificationClose() { + let pm = Services.perms; + let notificationURI = makeURI(notificationURL); + pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION); + + oldShowFavicons = Services.prefs.getBoolPref("alerts.showFavicons"); + Services.prefs.setBoolPref("alerts.showFavicons", true); + + yield PlacesTestUtils.addVisits(notificationURI); + let faviconURI = yield new Promise(resolve => { + let faviconURI = makeURI("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"); + PlacesUtils.favicons.setAndFetchFaviconForPage(notificationURI, faviconURI, + true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + (faviconURI, iconSize, iconData, mimeType) => resolve(faviconURI), + Services.scriptSecurityManager.getSystemPrincipal()); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: notificationURL + }, function* dummyTabTask(aBrowser) { + yield openNotification(aBrowser, "showNotification2"); + + info("Notification alert showing"); + + let alertWindow = Services.wm.getMostRecentWindow("alert:alert"); + if (!alertWindow) { + ok(true, "Notifications don't use XUL windows on all platforms."); + yield closeNotification(aBrowser); + return; + } + + let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel"); + is(alertTitleLabel.value, "Test title", "Title text of notification should be present"); + let alertTextLabel = alertWindow.document.getElementById("alertTextLabel"); + is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present"); + let alertIcon = alertWindow.document.getElementById("alertIcon"); + is(alertIcon.src, faviconURI.spec, "Icon of notification should be present"); + + let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton"); + is(alertCloseButton.localName, "toolbarbutton", "close button found"); + let promiseBeforeUnloadEvent = + BrowserTestUtils.waitForEvent(alertWindow, "beforeunload"); + let closedTime = alertWindow.Date.now(); + alertCloseButton.click(); + info("Clicked on close button"); + yield promiseBeforeUnloadEvent; + + ok(true, "Alert should close when the close button is clicked"); + let currentTime = alertWindow.Date.now(); + // The notification will self-close at 12 seconds, so this checks + // that the notification closed before the timeout. + ok(currentTime - closedTime < 5000, + "Close requested at " + closedTime + ", actually closed at " + currentTime); + }); +}); + +add_task(function* cleanup() { + Services.perms.remove(makeURI(notificationURL), "desktop-notification"); + if (typeof oldShowFavicons == "boolean") { + Services.prefs.setBoolPref("alerts.showFavicons", oldShowFavicons); + } +}); diff --git a/browser/base/content/test/alerts/browser_notification_do_not_disturb.js b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js new file mode 100644 index 000000000..92c689fd2 --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js @@ -0,0 +1,80 @@ +"use strict"; + +var tab; +var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + +const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"] + .getService(Ci.nsIAlertsService) + .QueryInterface(Ci.nsIAlertsDoNotDisturb); + +function test () { + waitForExplicitFinish(); + + try { + // Only run the test if the do-not-disturb + // interface has been implemented. + ALERT_SERVICE.manualDoNotDisturb; + ok(true, "Alert service implements do-not-disturb interface"); + } catch (e) { + ok(true, "Alert service doesn't implement do-not-disturb interface, exiting test"); + finish(); + return; + } + + let pm = Services.perms; + registerCleanupFunction(function() { + ALERT_SERVICE.manualDoNotDisturb = false; + pm.remove(makeURI(notificationURL), "desktop-notification"); + gBrowser.removeTab(tab); + window.restore(); + }); + + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + // Make sure that do-not-disturb is not enabled. + ok(!ALERT_SERVICE.manualDoNotDisturb, "Alert service should not be disabled when test starts"); + ALERT_SERVICE.manualDoNotDisturb = false; + + tab = gBrowser.addTab(notificationURL); + gBrowser.selectedTab = tab; + tab.linkedBrowser.addEventListener("load", onLoad, true); +} + +function onLoad() { + tab.linkedBrowser.removeEventListener("load", onLoad, true); + openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing); +} + +function onAlertShowing() { + info("Notification alert showing"); + + let alertWindow = Services.wm.getMostRecentWindow("alert:alert"); + if (!alertWindow) { + ok(true, "Notifications don't use XUL windows on all platforms."); + closeNotification(tab.linkedBrowser).then(finish); + return; + } + let doNotDisturbMenuItem = alertWindow.document.getElementById("doNotDisturbMenuItem"); + is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found"); + alertWindow.addEventListener("beforeunload", onAlertClosing); + doNotDisturbMenuItem.click(); + info("Clicked on do-not-disturb menuitem"); +} + +function onAlertClosing(event) { + event.target.removeEventListener("beforeunload", onAlertClosing); + + ok(ALERT_SERVICE.manualDoNotDisturb, "Alert service should be disabled after clicking menuitem"); + + // The notification should not appear, but there is + // no way from the client-side to know that it was + // blocked, except for waiting some time and realizing + // that the "onshow" event never fired. + openNotification(tab.linkedBrowser, "showNotification2", 2000) + .then(onAlert2Showing, finish); +} + +function onAlert2Showing() { + ok(false, "the second alert should not have been shown"); + closeNotification(tab.linkedBrowser).then(finish); +} diff --git a/browser/base/content/test/alerts/browser_notification_open_settings.js b/browser/base/content/test/alerts/browser_notification_open_settings.js new file mode 100644 index 000000000..5306fd90a --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_open_settings.js @@ -0,0 +1,58 @@ +"use strict"; + +var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + +add_task(function* test_settingsOpen_observer() { + info("Opening a dummy tab so openPreferences=>switchToTabHavingURI doesn't use the blank tab."); + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:robots" + }, function* dummyTabTask(aBrowser) { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content"); + info("simulate a notifications-open-settings notification"); + let uri = NetUtil.newURI("https://example.com"); + let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {}); + Services.obs.notifyObservers(principal, "notifications-open-settings", null); + let tab = yield tabPromise; + ok(tab, "The notification settings tab opened"); + yield BrowserTestUtils.removeTab(tab); + }); +}); + +add_task(function* test_settingsOpen_button() { + let pm = Services.perms; + info("Adding notification permission"); + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + try { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: notificationURL + }, function* tabTask(aBrowser) { + info("Waiting for notification"); + yield openNotification(aBrowser, "showNotification2"); + + let alertWindow = Services.wm.getMostRecentWindow("alert:alert"); + if (!alertWindow) { + ok(true, "Notifications don't use XUL windows on all platforms."); + yield closeNotification(aBrowser); + return; + } + + let closePromise = promiseWindowClosed(alertWindow); + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content"); + let openSettingsMenuItem = alertWindow.document.getElementById("openSettingsMenuItem"); + openSettingsMenuItem.click(); + + info("Waiting for notification settings tab"); + let tab = yield tabPromise; + ok(tab, "The notification settings tab opened"); + + yield closePromise; + yield BrowserTestUtils.removeTab(tab); + }); + } finally { + info("Removing notification permission"); + pm.remove(makeURI(notificationURL), "desktop-notification"); + } +}); diff --git a/browser/base/content/test/alerts/browser_notification_permission_migration.js b/browser/base/content/test/alerts/browser_notification_permission_migration.js new file mode 100644 index 000000000..b015e59a7 --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_permission_migration.js @@ -0,0 +1,45 @@ +const UI_VERSION = 32; + +var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"] + .getService(Ci.nsIObserver); +var notificationURI = makeURI("http://example.org"); +var pm = Services.perms; +var currentUIVersion; + +add_task(function* setup() { + currentUIVersion = Services.prefs.getIntPref("browser.migration.version"); + Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1); + pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION); +}); + +add_task(function* test_permissionMigration() { + if ("@mozilla.org/system-alerts-service;1" in Cc) { + ok(true, "Notifications don't use XUL windows on all platforms."); + return; + } + + info("Waiting for migration notification"); + let alertWindowPromise = promiseAlertWindow(); + gBrowserGlue.observe(null, "browser-glue-test", "force-ui-migration"); + let alertWindow = yield alertWindowPromise; + + info("Clicking on notification"); + let url = + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "push#w_upgraded-notifications"; + let closePromise = promiseWindowClosed(alertWindow); + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url); + EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow); + + info("Waiting for migration info tab"); + let tab = yield tabPromise; + ok(tab, "The migration info tab opened"); + + yield closePromise; + yield BrowserTestUtils.removeTab(tab); +}); + +add_task(function* cleanup() { + Services.prefs.setIntPref("browser.migration.version", currentUIVersion); + pm.remove(notificationURI, "desktop-notification"); +}); diff --git a/browser/base/content/test/alerts/browser_notification_remove_permission.js b/browser/base/content/test/alerts/browser_notification_remove_permission.js new file mode 100644 index 000000000..bd36faeae --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_remove_permission.js @@ -0,0 +1,72 @@ +"use strict"; + +var tab; +var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; +var alertWindowClosed = false; +var permRemoved = false; + +function test () { + waitForExplicitFinish(); + + let pm = Services.perms; + registerCleanupFunction(function() { + pm.remove(makeURI(notificationURL), "desktop-notification"); + gBrowser.removeTab(tab); + window.restore(); + }); + + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + tab = gBrowser.addTab(notificationURL); + gBrowser.selectedTab = tab; + tab.linkedBrowser.addEventListener("load", onLoad, true); +} + +function onLoad() { + tab.linkedBrowser.removeEventListener("load", onLoad, true); + openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing); +} + +function onAlertShowing() { + info("Notification alert showing"); + + let alertWindow = Services.wm.getMostRecentWindow("alert:alert"); + if (!alertWindow) { + ok(true, "Notifications don't use XUL windows on all platforms."); + closeNotification(tab.linkedBrowser).then(finish); + return; + } + ok(Services.perms.testExactPermission(makeURI(notificationURL), "desktop-notification"), + "Permission should exist prior to removal"); + let disableForOriginMenuItem = alertWindow.document.getElementById("disableForOriginMenuItem"); + is(disableForOriginMenuItem.localName, "menuitem", "menuitem found"); + Services.obs.addObserver(permObserver, "perm-changed", false); + alertWindow.addEventListener("beforeunload", onAlertClosing); + disableForOriginMenuItem.click(); + info("Clicked on disable-for-origin menuitem") +} + +function permObserver(subject, topic, data) { + if (topic != "perm-changed") { + return; + } + + let permission = subject.QueryInterface(Ci.nsIPermission); + is(permission.type, "desktop-notification", "desktop-notification permission changed"); + is(data, "deleted", "desktop-notification permission deleted"); + + Services.obs.removeObserver(permObserver, "perm-changed"); + permRemoved = true; + if (alertWindowClosed) { + finish(); + } +} + +function onAlertClosing(event) { + event.target.removeEventListener("beforeunload", onAlertClosing); + + alertWindowClosed = true; + if (permRemoved) { + finish(); + } +} diff --git a/browser/base/content/test/alerts/browser_notification_replace.js b/browser/base/content/test/alerts/browser_notification_replace.js new file mode 100644 index 000000000..e678dc438 --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_replace.js @@ -0,0 +1,38 @@ +"use strict"; + +let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + +add_task(function* test_notificationReplace() { + let pm = Services.perms; + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: notificationURL + }, function* dummyTabTask(aBrowser) { + yield ContentTask.spawn(aBrowser, {}, function* () { + let win = content.window.wrappedJSObject; + let notification = win.showNotification1(); + let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close"); + + let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show"); + Assert.equal(showEvent.target.body, "Test body 1", "Showed tagged notification"); + + let newNotification = win.showNotification2(); + let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show"); + Assert.equal(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag"); + + let closeEvent = yield promiseCloseEvent; + Assert.equal(closeEvent.target.body, "Test body 1", "Closed previous tagged notification"); + + let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close"); + newNotification.close(); + let newCloseEvent = yield promiseNewCloseEvent; + Assert.equal(newCloseEvent.target.body, "Test body 2", "Closed new notification"); + }); + }); +}); + +add_task(function* cleanup() { + Services.perms.remove(makeURI(notificationURL), "desktop-notification"); +}); diff --git a/browser/base/content/test/alerts/browser_notification_tab_switching.js b/browser/base/content/test/alerts/browser_notification_tab_switching.js new file mode 100644 index 000000000..7e46c0722 --- /dev/null +++ b/browser/base/content/test/alerts/browser_notification_tab_switching.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +var tab; +var notification; +var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; +var newWindowOpenedFromTab; + +add_task(function* test_notificationPreventDefaultAndSwitchTabs() { + let pm = Services.perms; + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + let originalTab = gBrowser.selectedTab; + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: notificationURL + }, function* dummyTabTask(aBrowser) { + // Put new tab in background so it is obvious when it is re-focused. + yield BrowserTestUtils.switchTab(gBrowser, originalTab); + isnot(gBrowser.selectedBrowser, aBrowser, "Notification page loaded as a background tab"); + + // First, show a notification that will be have the tab-switching prevented. + function promiseNotificationEvent(evt) { + return ContentTask.spawn(aBrowser, evt, function* (evt) { + return yield new Promise(resolve => { + let notification = content.wrappedJSObject._notification; + notification.addEventListener(evt, function l(event) { + notification.removeEventListener(evt, l); + resolve({ defaultPrevented: event.defaultPrevented }); + }); + }); + }); + } + yield openNotification(aBrowser, "showNotification1"); + info("Notification alert showing"); + let alertWindow = Services.wm.getMostRecentWindow("alert:alert"); + if (!alertWindow) { + ok(true, "Notifications don't use XUL windows on all platforms."); + yield closeNotification(aBrowser); + return; + } + info("Clicking on notification"); + let promiseClickEvent = promiseNotificationEvent("click"); + + // NB: This executeSoon is needed to allow the non-e10s runs of this test + // a chance to set the event listener on the page. Otherwise, we + // synchronously fire the click event before we listen for the event. + executeSoon(() => { + EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), + {}, alertWindow); + }); + let clickEvent = yield promiseClickEvent; + ok(clickEvent.defaultPrevented, "The event handler for the first notification cancels the event"); + isnot(gBrowser.selectedBrowser, aBrowser, "Notification page still a background tab"); + let notificationClosed = promiseNotificationEvent("close"); + yield closeNotification(aBrowser); + yield notificationClosed; + + // Second, show a notification that will cause the tab to get switched. + yield openNotification(aBrowser, "showNotification2"); + alertWindow = Services.wm.getMostRecentWindow("alert:alert"); + let promiseTabSelect = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabSelect"); + EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), + {}, + alertWindow); + yield promiseTabSelect; + is(gBrowser.selectedBrowser.currentURI.spec, notificationURL, + "Clicking on the second notification should select its originating tab"); + notificationClosed = promiseNotificationEvent("close"); + yield closeNotification(aBrowser); + yield notificationClosed; + }); +}); + +add_task(function* cleanup() { + Services.perms.remove(makeURI(notificationURL), "desktop-notification"); +}); diff --git a/browser/base/content/test/alerts/file_dom_notifications.html b/browser/base/content/test/alerts/file_dom_notifications.html new file mode 100644 index 000000000..6deede8fc --- /dev/null +++ b/browser/base/content/test/alerts/file_dom_notifications.html @@ -0,0 +1,39 @@ +<html> +<head> +<meta charset="utf-8"> +<script> +"use strict"; + +function showNotification1() { + var options = { + dir: undefined, + lang: undefined, + body: "Test body 1", + tag: "Test tag", + icon: undefined, + }; + var n = new Notification("Test title", options); + n.addEventListener("click", function(event) { + event.preventDefault(); + }); + return n; +} + +function showNotification2() { + var options = { + dir: undefined, + lang: undefined, + body: "Test body 2", + tag: "Test tag", + icon: undefined, + }; + return new Notification("Test title", options); +} +</script> +</head> +<body> +<form id="notificationForm" onsubmit="showNotification();"> + <input type="submit" value="Show notification" id="submit"/> +</form> +</body> +</html> diff --git a/browser/base/content/test/alerts/head.js b/browser/base/content/test/alerts/head.js new file mode 100644 index 000000000..21257de31 --- /dev/null +++ b/browser/base/content/test/alerts/head.js @@ -0,0 +1,71 @@ +function promiseAlertWindow() { + return new Promise(function(resolve) { + let listener = { + onOpenWindow(window) { + let alertWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); + alertWindow.addEventListener("load", function onLoad() { + alertWindow.removeEventListener("load", onLoad); + let windowType = alertWindow.document.documentElement.getAttribute("windowtype"); + if (windowType != "alert:alert") { + return; + } + Services.wm.removeListener(listener); + resolve(alertWindow); + }); + }, + }; + Services.wm.addListener(listener); + }); +} + +/** + * Similar to `BrowserTestUtils.closeWindow`, but + * doesn't call `window.close()`. + */ +function promiseWindowClosed(window) { + return new Promise(function(resolve) { + Services.ww.registerNotification(function observer(subject, topic, data) { + if (topic == "domwindowclosed" && subject == window) { + Services.ww.unregisterNotification(observer); + resolve(); + } + }); + }); +} + +/** + * These two functions work with file_dom_notifications.html to open the + * notification and close it. + * + * |fn| can be showNotification1 or showNotification2. + * if |timeout| is passed, then the promise returned from this function is + * rejected after the requested number of miliseconds. + */ +function openNotification(aBrowser, fn, timeout) { + return ContentTask.spawn(aBrowser, { fn, timeout }, function* ({ fn, timeout }) { + let win = content.wrappedJSObject; + let notification = win[fn](); + win._notification = notification; + yield new Promise((resolve, reject) => { + function listener() { + notification.removeEventListener("show", listener); + resolve(); + } + + notification.addEventListener("show", listener); + + if (timeout) { + content.setTimeout(() => { + notification.removeEventListener("show", listener); + reject("timed out"); + }, timeout); + } + }); + }); +} + +function closeNotification(aBrowser) { + return ContentTask.spawn(aBrowser, null, function() { + content.wrappedJSObject._notification.close(); + }); +} diff --git a/browser/base/content/test/captivePortal/browser.ini b/browser/base/content/test/captivePortal/browser.ini new file mode 100644 index 000000000..cfdbc5c2f --- /dev/null +++ b/browser/base/content/test/captivePortal/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + head.js + +[browser_CaptivePortalWatcher.js] +skip-if = os == "win" # Bug 1313894 +[browser_CaptivePortalWatcher_1.js] +skip-if = os == "win" # Bug 1313894 +[browser_captivePortal_certErrorUI.js] diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js new file mode 100644 index 000000000..e9c0fad6d --- /dev/null +++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js @@ -0,0 +1,119 @@ +"use strict"; + +add_task(setupPrefsAndRecentWindowBehavior); + +// Each of the test cases below is run twice: once for login-success and once +// for login-abort (aSuccess set to true and false respectively). +let testCasesForBothSuccessAndAbort = [ + /** + * A portal is detected when there's no browser window, then a browser + * window is opened, then the portal is freed. + * The portal tab should be added and focused when the window is + * opened, and closed automatically when the success event is fired. + * The captive portal notification should be shown when the window is + * opened, and closed automatically when the success event is fired. + */ + function* test_detectedWithNoBrowserWindow_Open(aSuccess) { + yield portalDetected(); + let win = yield focusWindowAndWaitForPortalUI(); + yield freePortal(aSuccess); + ensureNoPortalTab(win); + ensureNoPortalNotification(win); + yield closeWindowAndWaitForXulWindowVisible(win); + }, + + /** + * A portal is detected when multiple browser windows are open but none + * have focus. A brower window is focused, then the portal is freed. + * The portal tab should be added and focused when the window is + * focused, and closed automatically when the success event is fired. + * The captive portal notification should be shown in all windows upon + * detection, and closed automatically when the success event is fired. + */ + function* test_detectedWithNoBrowserWindow_Focused(aSuccess) { + let win1 = yield openWindowAndWaitForFocus(); + let win2 = yield openWindowAndWaitForFocus(); + // Defocus both windows. + yield SimpleTest.promiseFocus(window); + + yield portalDetected(); + + // Notification should be shown in both windows. + ensurePortalNotification(win1); + ensureNoPortalTab(win1); + ensurePortalNotification(win2); + ensureNoPortalTab(win2); + + yield focusWindowAndWaitForPortalUI(false, win2); + + yield freePortal(aSuccess); + + ensureNoPortalNotification(win1); + ensureNoPortalTab(win2); + ensureNoPortalNotification(win2); + + yield closeWindowAndWaitForXulWindowVisible(win2); + // No need to wait for xul-window-visible: after win2 is closed, focus + // is restored to the default window and win1 remains in the background. + yield BrowserTestUtils.closeWindow(win1); + }, + + /** + * A portal is detected when there's no browser window, then a browser + * window is opened, then the portal is freed. + * The recheck triggered when the browser window is opened takes a + * long time. No portal tab should be added. + * The captive portal notification should be shown when the window is + * opened, and closed automatically when the success event is fired. + */ + function* test_detectedWithNoBrowserWindow_LongRecheck(aSuccess) { + yield portalDetected(); + let win = yield focusWindowAndWaitForPortalUI(true); + yield freePortal(aSuccess); + ensureNoPortalTab(win); + ensureNoPortalNotification(win); + yield closeWindowAndWaitForXulWindowVisible(win); + }, + + /** + * A portal is detected when there's no browser window, and the + * portal is freed before a browser window is opened. No portal + * UI should be shown when a browser window is opened. + */ + function* test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) { + yield portalDetected(); + yield freePortal(aSuccess); + let win = yield openWindowAndWaitForFocus(); + // Wait for a while to make sure no UI is shown. + yield new Promise(resolve => { + setTimeout(resolve, 1000); + }); + ensureNoPortalTab(win); + ensureNoPortalNotification(win); + yield closeWindowAndWaitForXulWindowVisible(win); + }, + + /** + * A portal is detected when a browser window has focus. No portal tab should + * be opened. A notification bar should be displayed in all browser windows. + */ + function* test_detectedWithFocus(aSuccess) { + let win1 = yield openWindowAndWaitForFocus(); + let win2 = yield openWindowAndWaitForFocus(); + yield portalDetected(); + ensureNoPortalTab(win1); + ensureNoPortalTab(win2); + ensurePortalNotification(win1); + ensurePortalNotification(win2); + yield freePortal(aSuccess); + ensureNoPortalNotification(win1); + ensureNoPortalNotification(win2); + yield closeWindowAndWaitForXulWindowVisible(win2); + yield closeWindowAndWaitForXulWindowVisible(win1); + }, +]; + +for (let testcase of testCasesForBothSuccessAndAbort) { + add_task(testcase.bind(null, true)); + add_task(testcase.bind(null, false)); +} diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js new file mode 100644 index 000000000..71b12c32a --- /dev/null +++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js @@ -0,0 +1,91 @@ +"use strict"; + +add_task(setupPrefsAndRecentWindowBehavior); + +let testcases = [ + /** + * A portal is detected when there's no browser window, + * then a browser window is opened, and the portal is logged into + * and redirects to a different page. The portal tab should be added + * and focused when the window is opened, and left open after login + * since it redirected. + */ + function* test_detectedWithNoBrowserWindow_Redirect() { + yield portalDetected(); + let win = yield focusWindowAndWaitForPortalUI(); + let browser = win.gBrowser.selectedTab.linkedBrowser; + let loadPromise = + BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED); + BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED); + yield loadPromise; + yield freePortal(true); + ensurePortalTab(win); + ensureNoPortalNotification(win); + yield closeWindowAndWaitForXulWindowVisible(win); + }, + + /** + * Test the various expected behaviors of the "Show Login Page" button + * in the captive portal notification. The button should be visible for + * all tabs except the captive portal tab, and when clicked, should + * ensure a captive portal tab is open and select it. + */ + function* test_showLoginPageButton() { + let win = yield openWindowAndWaitForFocus(); + yield portalDetected(); + let notification = ensurePortalNotification(win); + testShowLoginPageButtonVisibility(notification, "visible"); + + function testPortalTabSelectedAndButtonNotVisible() { + is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected."); + testShowLoginPageButtonVisibility(notification, "hidden"); + } + + let button = notification.querySelector("button.notification-button"); + function* clickButtonAndExpectNewPortalTab() { + let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL); + button.click(); + let tab = yield p; + is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected."); + return tab; + } + + // Simulate clicking the button. The portal tab should be opened and + // selected and the button should hide. + let tab = yield clickButtonAndExpectNewPortalTab(); + testPortalTabSelectedAndButtonNotVisible(); + + // Close the tab. The button should become visible. + yield BrowserTestUtils.removeTab(tab); + ensureNoPortalTab(win); + testShowLoginPageButtonVisibility(notification, "visible"); + + // When the button is clicked, a new portal tab should be opened and + // selected. + tab = yield clickButtonAndExpectNewPortalTab(); + + // Open another arbitrary tab. The button should become visible. When it's clicked, + // the portal tab should be selected. + let anotherTab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser); + testShowLoginPageButtonVisibility(notification, "visible"); + button.click(); + is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected."); + + // Close the portal tab and select the arbitrary tab. The button should become + // visible and when it's clicked, a new portal tab should be opened. + yield BrowserTestUtils.removeTab(tab); + win.gBrowser.selectedTab = anotherTab; + testShowLoginPageButtonVisibility(notification, "visible"); + tab = yield clickButtonAndExpectNewPortalTab(); + + yield BrowserTestUtils.removeTab(anotherTab); + yield freePortal(true); + ensureNoPortalTab(win); + ensureNoPortalNotification(win); + yield closeWindowAndWaitForXulWindowVisible(win); + }, +]; + +for (let testcase of testcases) { + add_task(testcase); +} diff --git a/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js new file mode 100644 index 000000000..6b97e19a3 --- /dev/null +++ b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BAD_CERT_PAGE = "https://expired.example.com/"; + +// This tests the alternate cert error UI when we are behind a captive portal. + +add_task(function* checkCaptivePortalCertErrorUI() { + yield SpecialPowers.pushPrefEnv({ + set: [["captivedetect.canonicalURL", CANONICAL_URL], + ["captivedetect.canonicalContent", CANONICAL_CONTENT]], + }); + + let captivePortalStatePropagated = TestUtils.topicObserved("ipc:network:captive-portal-set-state"); + + info("Checking that the alternate about:certerror UI is shown when we are behind a captive portal."); + Services.obs.notifyObservers(null, "captive-portal-login", null); + + info("Waiting for captive portal state to be propagated to the content process."); + yield captivePortalStatePropagated; + + // Open a page with a cert error. + let browser; + let certErrorLoaded; + let errorTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + let tab = gBrowser.addTab(BAD_CERT_PAGE); + gBrowser.selectedTab = tab; + browser = gBrowser.selectedBrowser; + certErrorLoaded = waitForCertErrorLoad(browser); + return tab; + }, false); + + info("Waiting for cert error page to load.") + yield certErrorLoaded; + + let portalTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, CANONICAL_URL); + + yield ContentTask.spawn(browser, null, () => { + let doc = content.document; + ok(doc.body.classList.contains("captiveportal"), + "Captive portal error page UI is visible."); + + info("Clicking the Open Login Page button."); + let loginButton = doc.getElementById("openPortalLoginPageButton"); + is(loginButton.getAttribute("autofocus"), "true", "openPortalLoginPageButton has autofocus"); + loginButton.click(); + }); + + let portalTab = yield portalTabPromise; + is(gBrowser.selectedTab, portalTab, "Login page should be open in a new foreground tab."); + + // Make sure clicking the "Open Login Page" button again focuses the existing portal tab. + yield BrowserTestUtils.switchTab(gBrowser, errorTab); + // Passing an empty function to BrowserTestUtils.switchTab lets us wait for an arbitrary + // tab switch. + portalTabPromise = BrowserTestUtils.switchTab(gBrowser, () => {}); + yield ContentTask.spawn(browser, null, () => { + info("Clicking the Open Login Page button."); + content.document.getElementById("openPortalLoginPageButton").click(); + }); + + let portalTab2 = yield portalTabPromise; + is(portalTab2, portalTab, "The existing portal tab should be focused."); + + let portalTabRemoved = BrowserTestUtils.removeTab(portalTab, {dontRemove: true}); + let errorTabReloaded = waitForCertErrorLoad(browser); + + Services.obs.notifyObservers(null, "captive-portal-login-success", null); + yield portalTabRemoved; + + info("Waiting for error tab to be reloaded after the captive portal was freed."); + yield errorTabReloaded; + yield ContentTask.spawn(browser, null, () => { + let doc = content.document; + ok(!doc.body.classList.contains("captiveportal"), + "Captive portal error page UI is not visible."); + }); + + yield BrowserTestUtils.removeTab(errorTab); +}); diff --git a/browser/base/content/test/captivePortal/head.js b/browser/base/content/test/captivePortal/head.js new file mode 100644 index 000000000..e40b5a325 --- /dev/null +++ b/browser/base/content/test/captivePortal/head.js @@ -0,0 +1,181 @@ +Components.utils.import("resource:///modules/RecentWindow.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "CaptivePortalWatcher", + "resource:///modules/CaptivePortalWatcher.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "cps", + "@mozilla.org/network/captive-portal-service;1", + "nsICaptivePortalService"); + +const CANONICAL_CONTENT = "success"; +const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT; +const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected"; +const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected"; + +function* setupPrefsAndRecentWindowBehavior() { + yield SpecialPowers.pushPrefEnv({ + set: [["captivedetect.canonicalURL", CANONICAL_URL], + ["captivedetect.canonicalContent", CANONICAL_CONTENT]], + }); + // We need to test behavior when a portal is detected when there is no browser + // window, but we can't close the default window opened by the test harness. + // Instead, we deactivate CaptivePortalWatcher in the default window and + // exclude it from RecentWindow.getMostRecentBrowserWindow in an attempt to + // mask its presence. + window.CaptivePortalWatcher.uninit(); + let getMostRecentBrowserWindowCopy = RecentWindow.getMostRecentBrowserWindow; + let defaultWindow = window; + RecentWindow.getMostRecentBrowserWindow = () => { + let win = getMostRecentBrowserWindowCopy(); + if (win == defaultWindow) { + return null; + } + return win; + }; + + registerCleanupFunction(function* cleanUp() { + RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindowCopy; + window.CaptivePortalWatcher.init(); + }); +} + +function* portalDetected() { + Services.obs.notifyObservers(null, "captive-portal-login", null); + yield BrowserTestUtils.waitForCondition(() => { + return cps.state == cps.LOCKED_PORTAL; + }, "Waiting for Captive Portal Service to update state after portal detected."); +} + +function* freePortal(aSuccess) { + Services.obs.notifyObservers(null, + "captive-portal-login-" + (aSuccess ? "success" : "abort"), null); + yield BrowserTestUtils.waitForCondition(() => { + return cps.state != cps.LOCKED_PORTAL; + }, "Waiting for Captive Portal Service to update state after portal freed."); +} + +// If a window is provided, it will be focused. Otherwise, a new window +// will be opened and focused. +function* focusWindowAndWaitForPortalUI(aLongRecheck, win) { + // CaptivePortalWatcher triggers a recheck when a window gains focus. If + // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS, + // a tab with the login page is opened and selected. If it took longer, + // no tab is opened. It's not reliable to time things in an async test, + // so use a delay threshold of -1 to simulate a long recheck (so that any + // amount of time is considered excessive), and a very large threshold to + // simulate a short recheck. + Preferences.set("captivedetect.portalRecheckDelayMS", aLongRecheck ? -1 : 1000000); + + if (!win) { + win = yield BrowserTestUtils.openNewBrowserWindow(); + } + yield SimpleTest.promiseFocus(win); + + // After a new window is opened, CaptivePortalWatcher asks for a recheck, and + // waits for it to complete. We need to manually tell it a recheck completed. + yield BrowserTestUtils.waitForCondition(() => { + return win.CaptivePortalWatcher._waitingForRecheck; + }, "Waiting for CaptivePortalWatcher to trigger a recheck."); + Services.obs.notifyObservers(null, "captive-portal-check-complete", null); + + let notification = ensurePortalNotification(win); + + if (aLongRecheck) { + ensureNoPortalTab(win); + testShowLoginPageButtonVisibility(notification, "visible"); + return win; + } + + let tab = win.gBrowser.tabs[1]; + if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) { + // The tab should load the canonical URL, wait for it. + yield BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL); + } + is(win.gBrowser.selectedTab, tab, + "The captive portal tab should be open and selected in the new window."); + testShowLoginPageButtonVisibility(notification, "hidden"); + return win; +} + +function ensurePortalTab(win) { + // For the tests that call this function, it's enough to ensure there + // are two tabs in the window - the default tab and the portal tab. + is(win.gBrowser.tabs.length, 2, + "There should be a captive portal tab in the window."); +} + +function ensurePortalNotification(win) { + let notificationBox = + win.document.getElementById("high-priority-global-notificationbox"); + let notification = notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE) + isnot(notification, null, + "There should be a captive portal notification in the window."); + return notification; +} + +// Helper to test whether the "Show Login Page" is visible in the captive portal +// notification (it should be hidden when the portal tab is selected). +function testShowLoginPageButtonVisibility(notification, visibility) { + let showLoginPageButton = notification.querySelector("button.notification-button"); + // If the visibility property was never changed from default, it will be + // an empty string, so we pretend it's "visible" (effectively the same). + is(showLoginPageButton.style.visibility || "visible", visibility, + "The \"Show Login Page\" button should be " + visibility + "."); +} + +function ensureNoPortalTab(win) { + is(win.gBrowser.tabs.length, 1, + "There should be no captive portal tab in the window."); +} + +function ensureNoPortalNotification(win) { + let notificationBox = + win.document.getElementById("high-priority-global-notificationbox"); + is(notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE), null, + "There should be no captive portal notification in the window."); +} + +/** + * Some tests open a new window and close it later. When the window is closed, + * the original window opened by mochitest gains focus, generating a + * xul-window-visible notification. If the next test also opens a new window + * before this notification has a chance to fire, CaptivePortalWatcher picks + * up the first one instead of the one from the new window. To avoid this + * unfortunate intermittent timing issue, we wait for the notification from + * the original window every time we close a window that we opened. + */ +function waitForXulWindowVisible() { + return new Promise(resolve => { + Services.obs.addObserver(function observe() { + Services.obs.removeObserver(observe, "xul-window-visible"); + resolve(); + }, "xul-window-visible", false); + }); +} + +function* closeWindowAndWaitForXulWindowVisible(win) { + let p = waitForXulWindowVisible(); + yield BrowserTestUtils.closeWindow(win); + yield p; +} + +/** + * BrowserTestUtils.openNewBrowserWindow() does not guarantee the newly + * opened window has received focus when the promise resolves, so we + * have to manually wait every time. + */ +function* openWindowAndWaitForFocus() { + let win = yield BrowserTestUtils.openNewBrowserWindow(); + yield SimpleTest.promiseFocus(win); + return win; +} + +function waitForCertErrorLoad(browser) { + return new Promise(resolve => { + info("Waiting for DOMContentLoaded event"); + browser.addEventListener("DOMContentLoaded", function load() { + browser.removeEventListener("DOMContentLoaded", load, false, true); + resolve(); + }, false, true); + }); +} diff --git a/browser/base/content/test/chrome/.eslintrc.js b/browser/base/content/test/chrome/.eslintrc.js new file mode 100644 index 000000000..8c0f4f574 --- /dev/null +++ b/browser/base/content/test/chrome/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/chrome/chrome.ini b/browser/base/content/test/chrome/chrome.ini new file mode 100644 index 000000000..15035fc0c --- /dev/null +++ b/browser/base/content/test/chrome/chrome.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_aboutCrashed.xul] diff --git a/browser/base/content/test/chrome/test_aboutCrashed.xul b/browser/base/content/test/chrome/test_aboutCrashed.xul new file mode 100644 index 000000000..7a68076f1 --- /dev/null +++ b/browser/base/content/test/chrome/test_aboutCrashed.xul @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <iframe type="content" id="frame1"/> + <iframe type="content" id="frame2" onload="doTest()"/> + <script type="application/javascript"><![CDATA[ + const Ci = Components.interfaces; + const Cu = Components.utils; + + Cu.import("resource://gre/modules/Services.jsm"); + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://gre/modules/Promise.jsm"); + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + + SimpleTest.waitForExplicitFinish(); + + // Load error pages do not fire "load" events, so let's use a progressListener. + function waitForErrorPage(frame) { + let errorPageDeferred = Promise.defer(); + + let progressListener = { + onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) { + if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) { + frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress) + .removeProgressListener(progressListener, + Ci.nsIWebProgress.NOTIFY_LOCATION); + + errorPageDeferred.resolve(); + } + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) + }; + + frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress) + .addProgressListener(progressListener, + Ci.nsIWebProgress.NOTIFY_LOCATION); + + return errorPageDeferred.promise; + } + + function doTest() { + Task.spawn(function test_aboutCrashed() { + let frame1 = document.getElementById("frame1"); + let frame2 = document.getElementById("frame2"); + let uri1 = Services.io.newURI("http://www.example.com/1", null, null); + let uri2 = Services.io.newURI("http://www.example.com/2", null, null); + + let errorPageReady = waitForErrorPage(frame1); + frame1.docShell.chromeEventHandler.setAttribute("crashedPageTitle", "pageTitle"); + frame1.docShell.displayLoadError(Components.results.NS_ERROR_CONTENT_CRASHED, uri1, null); + + yield errorPageReady; + frame1.docShell.chromeEventHandler.removeAttribute("crashedPageTitle"); + + SimpleTest.is(frame1.contentDocument.documentURI, + "about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&f=regular&d=pageTitle", + "Correct about:tabcrashed displayed for page with title."); + + errorPageReady = waitForErrorPage(frame2); + frame2.docShell.displayLoadError(Components.results.NS_ERROR_CONTENT_CRASHED, uri2, null); + + yield errorPageReady; + + SimpleTest.is(frame2.contentDocument.documentURI, + "about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&f=regular&d=%20", + "Correct about:tabcrashed displayed for page with no title."); + + SimpleTest.finish(); + }); + } + ]]></script> + + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" /> +</window> diff --git a/browser/base/content/test/general/.eslintrc.js b/browser/base/content/test/general/.eslintrc.js new file mode 100644 index 000000000..11abd6140 --- /dev/null +++ b/browser/base/content/test/general/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js", + "../../../../../testing/mochitest/mochitest.eslintrc.js", + ] +}; diff --git a/browser/base/content/test/general/POSTSearchEngine.xml b/browser/base/content/test/general/POSTSearchEngine.xml new file mode 100644 index 000000000..30567d92f --- /dev/null +++ b/browser/base/content/test/general/POSTSearchEngine.xml @@ -0,0 +1,6 @@ +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>POST Search</ShortName> + <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/base/content/test/general/print_postdata.sjs"> + <Param name="searchterms" value="{searchTerms}"/> + </Url> +</OpenSearchDescription> diff --git a/browser/base/content/test/general/aboutHome_content_script.js b/browser/base/content/test/general/aboutHome_content_script.js new file mode 100644 index 000000000..28d0e617e --- /dev/null +++ b/browser/base/content/test/general/aboutHome_content_script.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +addMessageListener("AboutHome:SearchTriggered", function (msg) { + sendAsyncMessage("AboutHomeTest:CheckRecordedSearch", msg.data); +}); diff --git a/browser/base/content/test/general/accounts_testRemoteCommands.html b/browser/base/content/test/general/accounts_testRemoteCommands.html new file mode 100644 index 000000000..517317aff --- /dev/null +++ b/browser/base/content/test/general/accounts_testRemoteCommands.html @@ -0,0 +1,83 @@ +<html>
+ <head>
+ <meta charset="utf-8">
+
+<script type="text/javascript;version=1.8">
+
+function init() {
+ window.addEventListener("message", function process(e) {doTest(e)}, false);
+ // unless we relinquish the eventloop,
+ // tests will run before the chrome event handlers are ready
+ setTimeout(doTest, 0);
+}
+
+function checkStatusValue(payload, expectedValue) {
+ return payload.status == expectedValue;
+}
+
+let tests = [
+{
+ info: "Check account log in",
+ event: "login",
+ data: {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: true
+ },
+ payloadType: "message",
+ validateResponse: function(payload) {
+ return checkStatusValue(payload, "login");
+ },
+},
+];
+
+let currentTest = -1;
+function doTest(evt) {
+ if (evt) {
+ if (currentTest < 0 || !evt.data.content)
+ return; // not yet testing
+
+ let test = tests[currentTest];
+ if (evt.data.type != test.payloadType)
+ return; // skip unrequested events
+
+ let error = JSON.stringify(evt.data.content);
+ let pass = false;
+ try {
+ pass = test.validateResponse(evt.data.content)
+ } catch (e) {}
+ reportResult(test.info, pass, error);
+ }
+ // start the next test if there are any left
+ if (tests[++currentTest])
+ sendToBrowser(tests[currentTest].event, tests[currentTest].data);
+ else
+ reportFinished();
+}
+
+function reportResult(info, pass, error) {
+ let data = {type: "testResult", info: info, pass: pass, error: error};
+ let event = new CustomEvent("FirefoxAccountsTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function reportFinished(cmd) {
+ let data = {type: "testsComplete", count: tests.length};
+ let event = new CustomEvent("FirefoxAccountsTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function sendToBrowser(type, data) {
+ let event = new CustomEvent("FirefoxAccountsCommand", {detail: {command: type, data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+</script>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/browser/base/content/test/general/alltabslistener.html b/browser/base/content/test/general/alltabslistener.html new file mode 100644 index 000000000..166c31037 --- /dev/null +++ b/browser/base/content/test/general/alltabslistener.html @@ -0,0 +1,8 @@ +<html> +<head> +<title>Test page for bug 463387</title> +</head> +<body> +<p>Test page for bug 463387</p> +</body> +</html> diff --git a/browser/base/content/test/general/app_bug575561.html b/browser/base/content/test/general/app_bug575561.html new file mode 100644 index 000000000..a60c7c87e --- /dev/null +++ b/browser/base/content/test/general/app_bug575561.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tabs</title>
+ </head>
+ <body>
+ <a href="http://example.com/browser/browser/base/content/test/general/dummy_page.html">same domain</a>
+ <a href="http://test1.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (different subdomain)</a>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html">different domain</a>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html" target="foo">different domain (with target)</a>
+ <a href="http://www.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (www prefix)</a>
+ <a href="data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>">data: URI</a>
+ <iframe src="app_subframe_bug575561.html"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/app_subframe_bug575561.html b/browser/base/content/test/general/app_subframe_bug575561.html new file mode 100644 index 000000000..8690497ff --- /dev/null +++ b/browser/base/content/test/general/app_subframe_bug575561.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=575561 +--> + <head> + <title>Test for links in app tab subframes</title> + </head> + <body> + <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html">different domain</a> + </body> +</html> diff --git a/browser/base/content/test/general/audio.ogg b/browser/base/content/test/general/audio.ogg Binary files differnew file mode 100644 index 000000000..477544875 --- /dev/null +++ b/browser/base/content/test/general/audio.ogg diff --git a/browser/base/content/test/general/benignPage.html b/browser/base/content/test/general/benignPage.html new file mode 100644 index 000000000..8e9429acd --- /dev/null +++ b/browser/base/content/test/general/benignPage.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <iframe src="http://not-tracking.example.com/"></iframe> + </body> +</html> diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini new file mode 100644 index 000000000..96e591ffe --- /dev/null +++ b/browser/base/content/test/general/browser.ini @@ -0,0 +1,494 @@ +[DEFAULT] +support-files = + POSTSearchEngine.xml + accounts_testRemoteCommands.html + alltabslistener.html + app_bug575561.html + app_subframe_bug575561.html + aboutHome_content_script.js + audio.ogg + browser_bug479408_sample.html + browser_bug678392-1.html + browser_bug678392-2.html + browser_bug970746.xhtml + browser_fxa_oauth.html + browser_fxa_oauth_with_keys.html + browser_fxa_web_channel.html + browser_registerProtocolHandler_notification.html + browser_star_hsts.sjs + browser_tab_dragdrop2_frame1.xul + browser_web_channel.html + browser_web_channel_iframe.html + bug1262648_string_with_newlines.dtd + bug592338.html + bug792517-2.html + bug792517.html + bug792517.sjs + bug839103.css + clipboard_pastefile.html + contextmenu_common.js + ctxmenu-image.png + discovery.html + download_page.html + dummy_page.html + feed_tab.html + file_generic_favicon.ico + file_with_favicon.html + file_bug822367_1.html + file_bug822367_1.js + file_bug822367_2.html + file_bug822367_3.html + file_bug822367_4.html + file_bug822367_4.js + file_bug822367_4B.html + file_bug822367_5.html + file_bug822367_6.html + file_bug902156.js + file_bug902156_1.html + file_bug902156_2.html + file_bug902156_3.html + file_bug906190_1.html + file_bug906190_2.html + file_bug906190_3_4.html + file_bug906190_redirected.html + file_bug906190.js + file_bug906190.sjs + file_mediaPlayback.html + file_mixedContentFromOnunload.html + file_mixedContentFromOnunload_test1.html + file_mixedContentFromOnunload_test2.html + file_mixedContentFramesOnHttp.html + file_mixedPassiveContent.html + file_bug970276_popup1.html + file_bug970276_popup2.html + file_bug970276_favicon1.ico + file_bug970276_favicon2.ico + file_documentnavigation_frameset.html + file_double_close_tab.html + file_favicon_change.html + file_favicon_change_not_in_document.html + file_fullscreen-window-open.html + head.js + healthreport_pingData.js + healthreport_testRemoteCommands.html + moz.png + navigating_window_with_download.html + offlineQuotaNotification.cacheManifest + offlineQuotaNotification.html + page_style_sample.html + parsingTestHelpers.jsm + pinning_headers.sjs + ssl_error_reports.sjs + print_postdata.sjs + searchSuggestionEngine.sjs + searchSuggestionEngine.xml + searchSuggestionEngine2.xml + subtst_contextmenu.html + subtst_contextmenu_input.html + subtst_contextmenu_xul.xul + test-mixedcontent-securityerrors.html + test_bug435035.html + test_bug462673.html + test_bug628179.html + test_bug839103.html + test_bug959531.html + test_process_flags_chrome.html + title_test.svg + unknownContentType_file.pif + unknownContentType_file.pif^headers^ + video.ogg + web_video.html + web_video1.ogv + web_video1.ogv^headers^ + zoom_test.html + test_no_mcb_on_http_site_img.html + test_no_mcb_on_http_site_img.css + test_no_mcb_on_http_site_font.html + test_no_mcb_on_http_site_font.css + test_no_mcb_on_http_site_font2.html + test_no_mcb_on_http_site_font2.css + test_mcb_redirect.html + test_mcb_redirect_image.html + test_mcb_double_redirect_image.html + test_mcb_redirect.js + test_mcb_redirect.sjs + file_bug1045809_1.html + file_bug1045809_2.html + file_csp_block_all_mixedcontent.html + file_csp_block_all_mixedcontent.js + !/image/test/mochitest/blue.png + !/toolkit/components/passwordmgr/test/browser/form_basic.html + !/toolkit/components/passwordmgr/test/browser/insecure_test.html + !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html + !/toolkit/content/tests/browser/common/mockTransfer.js + !/toolkit/modules/tests/browser/metadata_*.html + !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi + !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi + !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi + !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html + !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs + !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi + !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi + !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi + !/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs + +[browser_aboutAccounts.js] +skip-if = os == "linux" # Bug 958026 +support-files = + content_aboutAccounts.js +[browser_aboutCertError.js] +[browser_aboutNetError.js] +[browser_aboutSupport_newtab_security_state.js] +[browser_aboutHealthReport.js] +skip-if = os == "linux" # Bug 924307 +[browser_aboutHome.js] +[browser_aboutHome_wrapsCorrectly.js] +[browser_addKeywordSearch.js] +[browser_alltabslistener.js] +[browser_audioTabIcon.js] +tags = audiochannel +[browser_backButtonFitts.js] +skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X +[browser_beforeunload_duplicate_dialogs.js] +[browser_blob-channelname.js] +[browser_bookmark_popup.js] +skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds +[browser_bookmark_titles.js] +skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341) +[browser_bug321000.js] +subsuite = clipboard +skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528) +[browser_bug356571.js] +[browser_bug380960.js] +[browser_bug386835.js] +[browser_bug406216.js] +[browser_bug408415.js] +[browser_bug409481.js] +[browser_bug409624.js] +[browser_bug413915.js] +[browser_bug416661.js] +[browser_bug417483.js] +[browser_bug419612.js] +[browser_bug422590.js] +[browser_bug423833.js] +skip-if = true # bug 428712 +[browser_bug424101.js] +[browser_bug427559.js] +[browser_bug431826.js] +[browser_bug432599.js] +[browser_bug435035.js] +[browser_bug435325.js] +[browser_bug441778.js] +[browser_bug455852.js] +[browser_bug460146.js] +[browser_bug462289.js] +skip-if = toolkit == "cocoa" +[browser_bug462673.js] +[browser_bug477014.js] +[browser_bug479408.js] +[browser_bug481560.js] +[browser_bug484315.js] +[browser_bug491431.js] +[browser_bug495058.js] +[browser_bug517902.js] +skip-if = (os == 'linux' && e10s) # bug 1161699 +[browser_bug519216.js] +[browser_bug520538.js] +[browser_bug521216.js] +[browser_bug533232.js] +[browser_bug537013.js] +subsuite = clipboard +skip-if = e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab +[browser_bug537474.js] +[browser_bug550565.js] +[browser_bug553455.js] +[browser_bug555224.js] +[browser_bug555767.js] +[browser_bug559991.js] +[browser_bug561636.js] +skip-if = true # bug 1057615 +[browser_bug563588.js] +[browser_bug565575.js] +[browser_bug567306.js] +subsuite = clipboard +[browser_bug1261299.js] +subsuite = clipboard +skip-if = toolkit != "cocoa" # Because of tests for supporting Service Menu of macOS, bug 1261299 +[browser_bug1297539.js] +skip-if = toolkit != "cocoa" # Because of tests for supporting pasting from Service Menu of macOS, bug 1297539 +[browser_bug575561.js] +[browser_bug575830.js] +[browser_bug577121.js] +[browser_bug578534.js] +[browser_bug579872.js] +[browser_bug580638.js] +[browser_bug580956.js] +[browser_bug581242.js] +[browser_bug581253.js] +[browser_bug585558.js] +[browser_bug585785.js] +[browser_bug585830.js] +[browser_bug590206.js] +[browser_bug592338.js] +[browser_bug594131.js] +[browser_bug595507.js] +skip-if = true # bug 1057615 +[browser_bug596687.js] +[browser_bug597218.js] +[browser_bug609700.js] +[browser_bug623893.js] +[browser_bug624734.js] +[browser_bug633691.js] +[browser_bug647886.js] +[browser_bug655584.js] +[browser_bug664672.js] +[browser_bug676619.js] +skip-if = os == "mac" # mac: Intermittent failures, bug 925225 +[browser_bug678392.js] +skip-if = os == "mac" # Bug 1102331 - does focus things on the content window which break in e10s mode (still causes orange on Mac 10.10) +[browser_bug710878.js] +[browser_bug719271.js] +[browser_bug724239.js] +[browser_bug734076.js] +[browser_bug735471.js] +[browser_bug749738.js] +[browser_bug763468_perwindowpb.js] +[browser_bug767836_perwindowpb.js] +[browser_bug817947.js] +[browser_bug822367.js] +tags = mcb +[browser_bug832435.js] +[browser_bug839103.js] +[browser_bug882977.js] +[browser_bug902156.js] +tags = mcb +[browser_bug906190.js] +tags = mcb +[browser_mixedContentFromOnunload.js] +tags = mcb +[browser_mixedContentFramesOnHttp.js] +tags = mcb +[browser_bug970746.js] +[browser_bug1015721.js] +skip-if = os == 'win' +[browser_bug1064280_changeUrlInPinnedTab.js] +[browser_accesskeys.js] +[browser_clipboard.js] +subsuite = clipboard +[browser_clipboard_pastefile.js] +skip-if = true # Disabled due to the clipboard not supporting real file types yet (bug 1288773) +[browser_contentAreaClick.js] +skip-if = e10s # Clicks in content don't go through contentAreaClick with e10s. +[browser_contentAltClick.js] +[browser_contextmenu.js] +subsuite = clipboard +tags = fullscreen +skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558 +[browser_contextmenu_input.js] +skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558 +[browser_ctrlTab.js] +[browser_datachoices_notification.js] +skip-if = !datareporting +[browser_decoderDoctor.js] +skip-if = os == "mac" # decoder doctor isn't implemented on osx +[browser_devedition.js] +[browser_discovery.js] +[browser_double_close_tab.js] +[browser_documentnavigation.js] +[browser_duplicateIDs.js] +[browser_drag.js] +skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638. +[browser_favicon_change.js] +[browser_favicon_change_not_in_document.js] +[browser_findbarClose.js] +[browser_focusonkeydown.js] +[browser_fullscreen-window-open.js] +tags = fullscreen +skip-if = os == "linux" # Linux: Intermittent failures - bug 941575. +[browser_fxaccounts.js] +support-files = fxa_profile_handler.sjs +[browser_fxa_migrate.js] +[browser_fxa_oauth.js] +[browser_fxa_web_channel.js] +[browser_gestureSupport.js] +skip-if = e10s # Bug 863514 - no gesture support. +[browser_getshortcutoruri.js] +[browser_hide_removing.js] +[browser_homeDrop.js] +[browser_identity_UI.js] +[browser_insecureLoginForms.js] +support-files = insecure_opener.html +[browser_invalid_uri_back_forward_manipulation.js] +[browser_keywordBookmarklets.js] +[browser_keywordSearch.js] +[browser_keywordSearch_postData.js] +[browser_lastAccessedTab.js] +skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405) +[browser_menuButtonFitts.js] +skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376) +[browser_middleMouse_noJSPaste.js] +subsuite = clipboard +[browser_minimize.js] +[browser_misused_characters_in_strings.js] +[browser_mixed_content_cert_override.js] +[browser_mixedcontent_securityflags.js] +tags = mcb +[browser_modifiedclick_inherit_principal.js] +[browser_offlineQuotaNotification.js] +skip-if = os == "linux" && !debug # bug 1304273 +[browser_feed_discovery.js] +support-files = feed_discovery.html +[browser_gZipOfflineChild.js] +support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^ +[browser_overflowScroll.js] +[browser_pageInfo.js] +[browser_pageinfo_svg_image.js] +support-files = + svg_image.html +[browser_page_style_menu.js] +[browser_page_style_menu_update.js] +[browser_parsable_css.js] +skip-if = (debug || asan) # no point in running on both opt and debug, and will likely intermittently timeout on debug +[browser_parsable_script.js] +skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue. +[browser_permissions.js] +support-files = + permissions.html +[browser_pinnedTabs.js] +[browser_plainTextLinks.js] +[browser_printpreview.js] +[browser_private_browsing_window.js] +[browser_private_no_prompt.js] +[browser_purgehistory_clears_sh.js] +[browser_PageMetaData_pushstate.js] +[browser_refreshBlocker.js] +support-files = + refresh_header.sjs + refresh_meta.sjs +[browser_relatedTabs.js] +[browser_remoteTroubleshoot.js] +support-files = + test_remoteTroubleshoot.html +[browser_remoteWebNavigation_postdata.js] +[browser_removeTabsToTheEnd.js] +[browser_restore_isAppTab.js] +[browser_sanitize-passwordDisabledHosts.js] +[browser_sanitize-sitepermissions.js] +[browser_sanitize-timespans.js] +[browser_sanitizeDialog.js] +[browser_save_link-perwindowpb.js] +skip-if = e10s && debug && os == "win" # Bug 1280505 +[browser_save_private_link_perwindowpb.js] +[browser_save_link_when_window_navigates.js] +[browser_save_video.js] +[browser_save_video_frame.js] +[browser_scope.js] +[browser_contentSearchUI.js] +support-files = + contentSearchUI.html + contentSearchUI.js +[browser_selectpopup.js] +run-if = e10s +[browser_selectTabAtIndex.js] +[browser_ssl_error_reports.js] +[browser_star_hsts.js] +[browser_subframe_favicons_not_used.js] +[browser_syncui.js] +[browser_tab_close_dependent_window.js] +[browser_tabDrop.js] +[browser_tabReorder.js] +[browser_tab_detach_restore.js] +[browser_tab_drag_drop_perwindow.js] +[browser_tab_dragdrop.js] +skip-if = buildapp == 'mulet' || (e10s && (debug || os == 'linux')) # Bug 1312436 +[browser_tab_dragdrop2.js] +[browser_tabbar_big_widgets.js] +skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux + # Disabled on OS X because of bug 967917 +[browser_tabfocus.js] +[browser_tabkeynavigation.js] +skip-if = (os == "mac" && !e10s) # Bug 1237713 - OSX eats keypresses for some reason +[browser_tabopen_reflows.js] +[browser_tabs_close_beforeunload.js] +support-files = + close_beforeunload_opens_second_tab.html + close_beforeunload.html +[browser_tabs_isActive.js] +[browser_tabs_owner.js] +[browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js] +run-if = e10s +[browser_trackingUI_1.js] +tags = trackingprotection +support-files = + trackingPage.html + benignPage.html +[browser_trackingUI_2.js] +tags = trackingprotection +support-files = + trackingPage.html + benignPage.html +[browser_trackingUI_3.js] +tags = trackingprotection +[browser_trackingUI_4.js] +tags = trackingprotection +support-files = + trackingPage.html + benignPage.html +[browser_trackingUI_5.js] +tags = trackingprotection +support-files = + trackingPage.html +[browser_trackingUI_6.js] +tags = trackingprotection +support-files = + file_trackingUI_6.html + file_trackingUI_6.js + file_trackingUI_6.js^headers^ +[browser_trackingUI_telemetry.js] +tags = trackingprotection +support-files = + trackingPage.html +[browser_typeAheadFind.js] +[browser_unknownContentType_title.js] +[browser_unloaddialogs.js] +[browser_utilityOverlay.js] +[browser_viewSourceInTabOnViewSource.js] +[browser_visibleFindSelection.js] +[browser_visibleTabs.js] +[browser_visibleTabs_bookmarkAllPages.js] +skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined' +[browser_visibleTabs_bookmarkAllTabs.js] +[browser_visibleTabs_contextMenu.js] +[browser_visibleTabs_tabPreview.js] +skip-if = (os == "win" && !debug) +[browser_web_channel.js] +[browser_windowopen_reflows.js] +[browser_zbug569342.js] +skip-if = e10s || debug # Bug 1094240 - has findbar-related failures +[browser_registerProtocolHandler_notification.js] +[browser_no_mcb_on_http_site.js] +tags = mcb +[browser_addCertException.js] +[browser_bug1045809.js] +tags = mcb +[browser_e10s_switchbrowser.js] +[browser_e10s_about_process.js] +[browser_e10s_chrome_process.js] +[browser_e10s_javascript.js] +[browser_blockHPKP.js] +tags = psm +[browser_mcb_redirect.js] +tags = mcb +[browser_windowactivation.js] +[browser_contextmenu_childprocess.js] +[browser_bug963945.js] +[browser_domFullscreen_fullscreenMode.js] +tags = fullscreen +[browser_menuButtonBadgeManager.js] +[browser_newTabDrop.js] +[browser_newWindowDrop.js] +[browser_csp_block_all_mixedcontent.js] +tags = mcb +[browser_newwindow_focus.js] +skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason. +[browser_bug1299667.js] diff --git a/browser/base/content/test/general/browser_PageMetaData_pushstate.js b/browser/base/content/test/general/browser_PageMetaData_pushstate.js new file mode 100644 index 000000000..6f71c57a3 --- /dev/null +++ b/browser/base/content/test/general/browser_PageMetaData_pushstate.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(function* () { + let rooturi = "https://example.com/browser/toolkit/modules/tests/browser/"; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, rooturi + "metadata_simple.html"); + yield ContentTask.spawn(gBrowser.selectedBrowser, { rooturi }, function* (args) { + let result = PageMetadata.getData(content.document); + // Result should have description. + Assert.equal(result.url, args.rooturi + "metadata_simple.html", "metadata url is correct"); + Assert.equal(result.title, "Test Title", "metadata title is correct"); + Assert.equal(result.description, "A very simple test page", "description is correct"); + + content.history.pushState({}, "2", "2.html"); + result = PageMetadata.getData(content.document); + // Result should not have description. + Assert.equal(result.url, args.rooturi + "2.html", "metadata url is correct"); + Assert.equal(result.title, "Test Title", "metadata title is correct"); + Assert.ok(!result.description, "description is undefined"); + + Assert.equal(content.document.documentURI, args.rooturi + "2.html", + "content.document has correct url"); + }); + + is(gBrowser.currentURI.spec, rooturi + "2.html", "gBrowser has correct url"); + + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/general/browser_aboutAccounts.js b/browser/base/content/test/general/browser_aboutAccounts.js new file mode 100644 index 000000000..fd72a1608 --- /dev/null +++ b/browser/base/content/test/general/browser_aboutAccounts.js @@ -0,0 +1,499 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: window.location is null"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", + "resource://gre/modules/FxAccounts.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + +const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/"; +// Preference helpers. +var changedPrefs = new Set(); + +function setPref(name, value) { + changedPrefs.add(name); + Services.prefs.setCharPref(name, value); +} + +registerCleanupFunction(function() { + // Ensure we don't pollute prefs for next tests. + for (let name of changedPrefs) { + Services.prefs.clearUserPref(name); + } +}); + +var gTests = [ +{ + desc: "Test the remote commands", + teardown: function* () { + gBrowser.removeCurrentTab(); + yield signOut(); + }, + run: function* () + { + setPref("identity.fxaccounts.remote.signup.uri", + "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html"); + let tab = yield promiseNewTabLoadEvent("about:accounts"); + let mm = tab.linkedBrowser.messageManager; + + let deferred = Promise.defer(); + + // We'll get a message when openPrefs() is called, which this test should + // arrange. + let promisePrefsOpened = promiseOneMessage(tab, "test:openPrefsCalled"); + let results = 0; + try { + mm.addMessageListener("test:response", function responseHandler(msg) { + let data = msg.data.data; + if (data.type == "testResult") { + ok(data.pass, data.info); + results++; + } else if (data.type == "testsComplete") { + is(results, data.count, "Checking number of results received matches the number of tests that should have run"); + mm.removeMessageListener("test:response", responseHandler); + deferred.resolve(); + } + }); + } catch (e) { + ok(false, "Failed to get all commands"); + deferred.reject(); + } + yield deferred.promise; + yield promisePrefsOpened; + } +}, +{ + desc: "Test action=signin - no user logged in", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () + { + // When this loads with no user logged-in, we expect the "normal" URL + const expected_url = "https://example.com/?is_sign_in"; + setPref("identity.fxaccounts.remote.signin.uri", expected_url); + let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin"); + is(url, expected_url, "action=signin got the expected URL"); + // we expect the remote iframe to be shown. + yield checkVisibilities(tab, { + stage: false, // parent of 'manage' and 'intro' + manage: false, + intro: false, // this is "get started" + remote: true, + networkError: false + }); + } +}, +{ + desc: "Test action=signin - user logged in", + teardown: function* () { + gBrowser.removeCurrentTab(); + yield signOut(); + }, + run: function* () + { + // When this loads with a user logged-in, we expect the normal URL to + // have been ignored and the "manage" page to be shown. + const expected_url = "https://example.com/?is_sign_in"; + setPref("identity.fxaccounts.remote.signin.uri", expected_url); + yield setSignedInUser(); + let tab = yield promiseNewTabLoadEvent("about:accounts?action=signin"); + // about:accounts initializes after fetching the current user from Fxa - + // so we also request it - by the time we get it we know it should have + // done its thing. + yield fxAccounts.getSignedInUser(); + // we expect "manage" to be shown. + yield checkVisibilities(tab, { + stage: true, // parent of 'manage' and 'intro' + manage: true, + intro: false, // this is "get started" + remote: false, + networkError: false + }); + } +}, +{ + desc: "Test action=signin - captive portal", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () + { + const signinUrl = "https://redirproxy.example.com/test"; + setPref("identity.fxaccounts.remote.signin.uri", signinUrl); + let [tab, ] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin"); + yield checkVisibilities(tab, { + stage: true, // parent of 'manage' and 'intro' + manage: false, + intro: false, // this is "get started" + remote: false, + networkError: true + }); + } +}, +{ + desc: "Test action=signin - offline", + teardown: () => { + gBrowser.removeCurrentTab(); + BrowserOffline.toggleOfflineStatus(); + }, + run: function* () + { + BrowserOffline.toggleOfflineStatus(); + Services.cache2.clear(); + + const signinUrl = "https://unknowndomain.cow"; + setPref("identity.fxaccounts.remote.signin.uri", signinUrl); + let [tab, ] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin"); + yield checkVisibilities(tab, { + stage: true, // parent of 'manage' and 'intro' + manage: false, + intro: false, // this is "get started" + remote: false, + networkError: true + }); + } +}, +{ + desc: "Test action=signup - no user logged in", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () + { + const expected_url = "https://example.com/?is_sign_up"; + setPref("identity.fxaccounts.remote.signup.uri", expected_url); + let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signup"); + is(url, expected_url, "action=signup got the expected URL"); + // we expect the remote iframe to be shown. + yield checkVisibilities(tab, { + stage: false, // parent of 'manage' and 'intro' + manage: false, + intro: false, // this is "get started" + remote: true, + networkError: false + }); + }, +}, +{ + desc: "Test action=signup - user logged in", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () + { + const expected_url = "https://example.com/?is_sign_up"; + setPref("identity.fxaccounts.remote.signup.uri", expected_url); + yield setSignedInUser(); + let tab = yield promiseNewTabLoadEvent("about:accounts?action=signup"); + yield fxAccounts.getSignedInUser(); + // we expect "manage" to be shown. + yield checkVisibilities(tab, { + stage: true, // parent of 'manage' and 'intro' + manage: true, + intro: false, // this is "get started" + remote: false, + networkError: false + }); + }, +}, +{ + desc: "Test action=reauth", + teardown: function* () { + gBrowser.removeCurrentTab(); + yield signOut(); + }, + run: function* () + { + const expected_url = "https://example.com/?is_force_auth"; + setPref("identity.fxaccounts.remote.force_auth.uri", expected_url); + + yield setSignedInUser(); + let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=reauth"); + // The current user will be appended to the url + let expected = expected_url + "&email=foo%40example.com"; + is(url, expected, "action=reauth got the expected URL"); + }, +}, +{ + desc: "Test with migrateToDevEdition enabled (success)", + teardown: function* () { + gBrowser.removeCurrentTab(); + yield signOut(); + }, + run: function* () + { + let fxAccountsCommon = {}; + Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon); + const pref = "identity.fxaccounts.migrateToDevEdition"; + changedPrefs.add(pref); + Services.prefs.setBoolPref(pref, true); + + // Create the signedInUser.json file that will be used as the source of + // migrated user data. + let signedInUser = { + version: 1, + accountData: { + email: "foo@example.com", + uid: "1234@lcip.org", + sessionToken: "dead", + verified: true + } + }; + // We use a sub-dir of the real profile dir as the "pretend" profile dir + // for this test. + let profD = Services.dirsvc.get("ProfD", Ci.nsIFile); + let mockDir = profD.clone(); + mockDir.append("about-accounts-mock-profd"); + mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + let fxAccountsStorage = OS.Path.join(mockDir.path, fxAccountsCommon.DEFAULT_STORAGE_FILENAME); + yield OS.File.writeAtomic(fxAccountsStorage, JSON.stringify(signedInUser)); + info("Wrote file " + fxAccountsStorage); + + // this is a little subtle - we load about:robots so we get a non-remote + // tab, then we send a message which does both (a) load the URL we want and + // (b) mocks the default profile path used by about:accounts. + let tab = yield promiseNewTabLoadEvent("about:robots"); + let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response"); + + let mm = tab.linkedBrowser.messageManager; + mm.sendAsyncMessage("test:load-with-mocked-profile-path", { + url: "about:accounts", + profilePath: mockDir.path, + }); + + let response = yield readyPromise; + // We are expecting the iframe to be on the "force reauth" URL + let expected = yield fxAccounts.promiseAccountsForceSigninURI(); + is(response.data.url, expected); + + let userData = yield fxAccounts.getSignedInUser(); + SimpleTest.isDeeply(userData, signedInUser.accountData, "All account data were migrated"); + // The migration pref will have been switched off by now. + is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value"); + + yield OS.File.remove(fxAccountsStorage); + yield OS.File.removeEmptyDir(mockDir.path); + }, +}, +{ + desc: "Test with migrateToDevEdition enabled (no user to migrate)", + teardown: function* () { + gBrowser.removeCurrentTab(); + yield signOut(); + }, + run: function* () + { + const pref = "identity.fxaccounts.migrateToDevEdition"; + changedPrefs.add(pref); + Services.prefs.setBoolPref(pref, true); + + let profD = Services.dirsvc.get("ProfD", Ci.nsIFile); + let mockDir = profD.clone(); + mockDir.append("about-accounts-mock-profd"); + mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + // but leave it empty, so we don't think a user is logged in. + + let tab = yield promiseNewTabLoadEvent("about:robots"); + let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response"); + + let mm = tab.linkedBrowser.messageManager; + mm.sendAsyncMessage("test:load-with-mocked-profile-path", { + url: "about:accounts", + profilePath: mockDir.path, + }); + + let response = yield readyPromise; + // We are expecting the iframe to be on the "signup" URL + let expected = yield fxAccounts.promiseAccountsSignUpURI(); + is(response.data.url, expected); + + // and expect no signed in user. + let userData = yield fxAccounts.getSignedInUser(); + is(userData, null); + // The migration pref should have still been switched off. + is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value"); + yield OS.File.removeEmptyDir(mockDir.path); + }, +}, +{ + desc: "Test observers about:accounts", + teardown: function() { + gBrowser.removeCurrentTab(); + }, + run: function* () { + setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/"); + yield setSignedInUser(); + let tab = yield promiseNewTabLoadEvent("about:accounts"); + // sign the user out - the tab should have action=signin + yield signOut(); + // wait for the new load. + yield promiseOneMessage(tab, "test:document:load"); + is(tab.linkedBrowser.contentDocument.location.href, "about:accounts?action=signin"); + } +}, +{ + desc: "Test entrypoint query string, no action, no user logged in", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () { + // When this loads with no user logged-in, we expect the "normal" URL + setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/"); + let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?entrypoint=abouthome"); + is(url, "https://example.com/?entrypoint=abouthome", "entrypoint=abouthome got the expected URL"); + }, +}, +{ + desc: "Test entrypoint query string for signin", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () { + // When this loads with no user logged-in, we expect the "normal" URL + const expected_url = "https://example.com/?is_sign_in"; + setPref("identity.fxaccounts.remote.signin.uri", expected_url); + let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin&entrypoint=abouthome"); + is(url, expected_url + "&entrypoint=abouthome", "entrypoint=abouthome got the expected URL"); + }, +}, +{ + desc: "Test entrypoint query string for signup", + teardown: () => gBrowser.removeCurrentTab(), + run: function* () { + // When this loads with no user logged-in, we expect the "normal" URL + const sign_up_url = "https://example.com/?is_sign_up"; + setPref("identity.fxaccounts.remote.signup.uri", sign_up_url); + let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?entrypoint=abouthome&action=signup"); + is(url, sign_up_url + "&entrypoint=abouthome", "entrypoint=abouthome got the expected URL"); + }, +}, +{ + desc: "about:accounts URL params should be copied to remote URL params " + + "when remote URL has no URL params, except for 'action'", + teardown() { + gBrowser.removeCurrentTab(); + }, + run: function* () { + let signupURL = "https://example.com/"; + setPref("identity.fxaccounts.remote.signup.uri", signupURL); + let queryStr = "email=foo%40example.com&foo=bar&baz=quux"; + let [, url] = + yield promiseNewTabWithIframeLoadEvent("about:accounts?" + queryStr + + "&action=action"); + is(url, signupURL + "?" + queryStr, "URL params are copied to signup URL"); + }, +}, +{ + desc: "about:accounts URL params should be copied to remote URL params " + + "when remote URL already has some URL params, except for 'action'", + teardown() { + gBrowser.removeCurrentTab(); + }, + run: function* () { + let signupURL = "https://example.com/?param"; + setPref("identity.fxaccounts.remote.signup.uri", signupURL); + let queryStr = "email=foo%40example.com&foo=bar&baz=quux"; + let [, url] = + yield promiseNewTabWithIframeLoadEvent("about:accounts?" + queryStr + + "&action=action"); + is(url, signupURL + "&" + queryStr, "URL params are copied to signup URL"); + }, +}, +]; // gTests + +function test() +{ + waitForExplicitFinish(); + + Task.spawn(function* () { + for (let testCase of gTests) { + info(testCase.desc); + try { + yield testCase.run(); + } finally { + yield testCase.teardown(); + } + } + + finish(); + }); +} + +function promiseOneMessage(tab, messageName) { + let mm = tab.linkedBrowser.messageManager; + let deferred = Promise.defer(); + mm.addMessageListener(messageName, function onmessage(message) { + mm.removeMessageListener(messageName, onmessage); + deferred.resolve(message); + }); + return deferred.promise; +} + +function promiseNewTabLoadEvent(aUrl) +{ + let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl); + let browser = tab.linkedBrowser; + let mm = browser.messageManager; + + // give it an e10s-friendly content script to help with our tests. + mm.loadFrameScript(CHROME_BASE + "content_aboutAccounts.js", true); + // and wait for it to tell us about the load. + return promiseOneMessage(tab, "test:document:load").then( + () => tab + ); +} + +// Returns a promise which is resolved with the iframe's URL after a new +// tab is created and the iframe in that tab loads. +function promiseNewTabWithIframeLoadEvent(aUrl) { + let deferred = Promise.defer(); + let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl); + let browser = tab.linkedBrowser; + let mm = browser.messageManager; + + // give it an e10s-friendly content script to help with our tests. + mm.loadFrameScript(CHROME_BASE + "content_aboutAccounts.js", true); + // and wait for it to tell us about the iframe load. + mm.addMessageListener("test:iframe:load", function onFrameLoad(message) { + mm.removeMessageListener("test:iframe:load", onFrameLoad); + deferred.resolve([tab, message.data.url]); + }); + return deferred.promise; +} + +function checkVisibilities(tab, data) { + let ids = Object.keys(data); + let mm = tab.linkedBrowser.messageManager; + let deferred = Promise.defer(); + mm.addMessageListener("test:check-visibilities-response", function onResponse(message) { + mm.removeMessageListener("test:check-visibilities-response", onResponse); + for (let id of ids) { + is(message.data[id], data[id], "Element '" + id + "' has correct visibility"); + } + deferred.resolve(); + }); + mm.sendAsyncMessage("test:check-visibilities", {ids: ids}); + return deferred.promise; +} + +// watch out - these will fire observers which if you aren't careful, may +// interfere with the tests. +function setSignedInUser(data) { + if (!data) { + data = { + email: "foo@example.com", + uid: "1234@lcip.org", + assertion: "foobar", + sessionToken: "dead", + kA: "beef", + kB: "cafe", + verified: true + } + } + return fxAccounts.setSignedInUser(data); +} + +function signOut() { + // we always want a "localOnly" signout here... + return fxAccounts.signOut(true); +} diff --git a/browser/base/content/test/general/browser_aboutCertError.js b/browser/base/content/test/general/browser_aboutCertError.js new file mode 100644 index 000000000..0e335066c --- /dev/null +++ b/browser/base/content/test/general/browser_aboutCertError.js @@ -0,0 +1,409 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This is testing the aboutCertError page (Bug 1207107). + +const GOOD_PAGE = "https://example.com/"; +const BAD_CERT = "https://expired.example.com/"; +const UNKNOWN_ISSUER = "https://self-signed.example.com "; +const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443"; +const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); +const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + +add_task(function* checkReturnToAboutHome() { + info("Loading a bad cert page directly and making sure 'return to previous page' goes to about:home"); + let browser; + let certErrorLoaded; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + gBrowser.selectedTab = gBrowser.addTab(BAD_CERT); + browser = gBrowser.selectedBrowser; + certErrorLoaded = waitForCertErrorLoad(browser); + }, false); + + info("Loading and waiting for the cert error"); + yield certErrorLoaded; + + is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack"); + is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward"); + + // Populate the shistory entries manually, since it happens asynchronously + // and the following tests will be too soon otherwise. + yield TabStateFlusher.flush(browser); + let {entries} = JSON.parse(ss.getTabState(tab)); + is(entries.length, 1, "there is one shistory entry"); + + info("Clicking the go back button on about:certerror"); + yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let returnButton = doc.getElementById("returnButton"); + is(returnButton.getAttribute("autofocus"), "true", "returnButton has autofocus"); + returnButton.click(); + + yield ContentTaskUtils.waitForEvent(this, "pageshow", true); + }); + + is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); + is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward"); + is(gBrowser.currentURI.spec, "about:home", "Went back"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(function* checkReturnToPreviousPage() { + info("Loading a bad cert page and making sure 'return to previous page' goes back"); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); + let browser = gBrowser.selectedBrowser; + + info("Loading and waiting for the cert error"); + let certErrorLoaded = waitForCertErrorLoad(browser); + BrowserTestUtils.loadURI(browser, BAD_CERT); + yield certErrorLoaded; + + is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); + is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward"); + + // Populate the shistory entries manually, since it happens asynchronously + // and the following tests will be too soon otherwise. + yield TabStateFlusher.flush(browser); + let {entries} = JSON.parse(ss.getTabState(tab)); + is(entries.length, 2, "there are two shistory entries"); + + info("Clicking the go back button on about:certerror"); + yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let returnButton = doc.getElementById("returnButton"); + returnButton.click(); + + yield ContentTaskUtils.waitForEvent(this, "pageshow", true); + }); + + is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack"); + is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward"); + is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(function* checkBadStsCert() { + info("Loading a badStsCert and making sure exception button doesn't show up"); + yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); + let browser = gBrowser.selectedBrowser; + + info("Loading and waiting for the cert error"); + let certErrorLoaded = waitForCertErrorLoad(browser); + BrowserTestUtils.loadURI(browser, BAD_STS_CERT); + yield certErrorLoaded; + + let exceptionButtonHidden = yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let exceptionButton = doc.getElementById("exceptionDialogButton"); + return exceptionButton.hidden; + }); + ok(exceptionButtonHidden, "Exception button is hidden"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds"; + +add_task(function* checkWrongSystemTimeWarning() { + function* setUpPage() { + let browser; + let certErrorLoaded; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + gBrowser.selectedTab = gBrowser.addTab(BAD_CERT); + browser = gBrowser.selectedBrowser; + certErrorLoaded = waitForCertErrorLoad(browser); + }, false); + + info("Loading and waiting for the cert error"); + yield certErrorLoaded; + + return yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let div = doc.getElementById("wrongSystemTimePanel"); + let systemDateDiv = doc.getElementById("wrongSystemTime_systemDate"); + let actualDateDiv = doc.getElementById("wrongSystemTime_actualDate"); + let learnMoreLink = doc.getElementById("learnMoreLink"); + + return { + divDisplay: content.getComputedStyle(div).display, + text: div.textContent, + systemDate: systemDateDiv.textContent, + actualDate: actualDateDiv.textContent, + learnMoreLink: learnMoreLink.href + }; + }); + } + + let formatter = new Intl.DateTimeFormat(); + + // pretend we have a positively skewed (ahead) system time + let serverDate = new Date("2015/10/27"); + let serverDateFmt = formatter.format(serverDate); + let localDateFmt = formatter.format(new Date()); + + let skew = Math.floor((Date.now() - serverDate.getTime()) / 1000); + yield new Promise(r => SpecialPowers.pushPrefEnv({set: + [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r)); + + info("Loading a bad cert page with a skewed clock"); + let message = yield Task.spawn(setUpPage); + + isnot(message.divDisplay, "none", "Wrong time message information is visible"); + ok(message.text.includes("because your clock appears to show the wrong time"), + "Correct error message found"); + ok(message.text.includes("expired.example.com"), "URL found in error message"); + ok(message.systemDate.includes(localDateFmt), "correct local date displayed"); + ok(message.actualDate.includes(serverDateFmt), "correct server date displayed"); + ok(message.learnMoreLink.includes("time-errors"), "time-errors in the Learn More URL"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // pretend we have a negatively skewed (behind) system time + serverDate = new Date(); + serverDate.setYear(serverDate.getFullYear() + 1); + serverDateFmt = formatter.format(serverDate); + + skew = Math.floor((Date.now() - serverDate.getTime()) / 1000); + yield new Promise(r => SpecialPowers.pushPrefEnv({set: + [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r)); + + info("Loading a bad cert page with a skewed clock"); + message = yield Task.spawn(setUpPage); + + isnot(message.divDisplay, "none", "Wrong time message information is visible"); + ok(message.text.includes("because your clock appears to show the wrong time"), + "Correct error message found"); + ok(message.text.includes("expired.example.com"), "URL found in error message"); + ok(message.systemDate.includes(localDateFmt), "correct local date displayed"); + ok(message.actualDate.includes(serverDateFmt), "correct server date displayed"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // pretend we only have a slightly skewed system time, four hours + skew = 60 * 60 * 4; + yield new Promise(r => SpecialPowers.pushPrefEnv({set: + [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r)); + + info("Loading a bad cert page with an only slightly skewed clock"); + message = yield Task.spawn(setUpPage); + + is(message.divDisplay, "none", "Wrong time message information is not visible"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // now pretend we have no skewed system time + skew = 0; + yield new Promise(r => SpecialPowers.pushPrefEnv({set: + [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r)); + + info("Loading a bad cert page with no skewed clock"); + message = yield Task.spawn(setUpPage); + + is(message.divDisplay, "none", "Wrong time message information is not visible"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(function* checkAdvancedDetails() { + info("Loading a bad cert page and verifying the main error and advanced details section"); + let browser; + let certErrorLoaded; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + gBrowser.selectedTab = gBrowser.addTab(BAD_CERT); + browser = gBrowser.selectedBrowser; + certErrorLoaded = waitForCertErrorLoad(browser); + }, false); + + info("Loading and waiting for the cert error"); + yield certErrorLoaded; + + let message = yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let shortDescText = doc.getElementById("errorShortDescText"); + info("Main error text: " + shortDescText.textContent); + ok(shortDescText.textContent.includes("expired.example.com"), + "Should list hostname in error message."); + + let advancedButton = doc.getElementById("advancedButton"); + advancedButton.click(); + let el = doc.getElementById("errorCode"); + return { textContent: el.textContent, tagName: el.tagName }; + }); + is(message.textContent, "SEC_ERROR_EXPIRED_CERTIFICATE", + "Correct error message found"); + is(message.tagName, "a", "Error message is a link"); + + message = yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let errorCode = doc.getElementById("errorCode"); + errorCode.click(); + let div = doc.getElementById("certificateErrorDebugInformation"); + let text = doc.getElementById("certificateErrorText"); + + let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + let serializable = docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + let serializedSecurityInfo = serhelper.serializeToString(serializable); + return { + divDisplay: content.getComputedStyle(div).display, + text: text.textContent, + securityInfoAsString: serializedSecurityInfo + }; + }); + isnot(message.divDisplay, "none", "Debug information is visible"); + ok(message.text.includes(BAD_CERT), "Correct URL found"); + ok(message.text.includes("Certificate has expired"), + "Correct error message found"); + ok(message.text.includes("HTTP Strict Transport Security: false"), + "Correct HSTS value found"); + ok(message.text.includes("HTTP Public Key Pinning: false"), + "Correct HPKP value found"); + let certChain = getCertChain(message.securityInfoAsString); + ok(message.text.includes(certChain), "Found certificate chain"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(function* checkAdvancedDetailsForHSTS() { + info("Loading a bad STS cert page and verifying the advanced details section"); + let browser; + let certErrorLoaded; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + gBrowser.selectedTab = gBrowser.addTab(BAD_STS_CERT); + browser = gBrowser.selectedBrowser; + certErrorLoaded = waitForCertErrorLoad(browser); + }, false); + + info("Loading and waiting for the cert error"); + yield certErrorLoaded; + + let message = yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let advancedButton = doc.getElementById("advancedButton"); + advancedButton.click(); + let ec = doc.getElementById("errorCode"); + let cdl = doc.getElementById("cert_domain_link"); + return { + ecTextContent: ec.textContent, + ecTagName: ec.tagName, + cdlTextContent: cdl.textContent, + cdlTagName: cdl.tagName + }; + }); + + const badStsUri = Services.io.newURI(BAD_STS_CERT, null, null); + is(message.ecTextContent, "SSL_ERROR_BAD_CERT_DOMAIN", + "Correct error message found"); + is(message.ecTagName, "a", "Error message is a link"); + const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1); + is(message.cdlTextContent, url, + "Correct cert_domain_link contents found"); + is(message.cdlTagName, "a", "cert_domain_link is a link"); + + message = yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let errorCode = doc.getElementById("errorCode"); + errorCode.click(); + let div = doc.getElementById("certificateErrorDebugInformation"); + let text = doc.getElementById("certificateErrorText"); + + let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + let serializable = docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + let serializedSecurityInfo = serhelper.serializeToString(serializable); + return { + divDisplay: content.getComputedStyle(div).display, + text: text.textContent, + securityInfoAsString: serializedSecurityInfo + }; + }); + isnot(message.divDisplay, "none", "Debug information is visible"); + ok(message.text.includes(badStsUri.spec), "Correct URL found"); + ok(message.text.includes("requested domain name does not match the server\u2019s certificate"), + "Correct error message found"); + ok(message.text.includes("HTTP Strict Transport Security: false"), + "Correct HSTS value found"); + ok(message.text.includes("HTTP Public Key Pinning: true"), + "Correct HPKP value found"); + let certChain = getCertChain(message.securityInfoAsString); + ok(message.text.includes(certChain), "Found certificate chain"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(function* checkUnknownIssuerLearnMoreLink() { + info("Loading a cert error for self-signed pages and checking the correct link is shown"); + let browser; + let certErrorLoaded; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + gBrowser.selectedTab = gBrowser.addTab(UNKNOWN_ISSUER); + browser = gBrowser.selectedBrowser; + certErrorLoaded = waitForCertErrorLoad(browser); + }, false); + + info("Loading and waiting for the cert error"); + yield certErrorLoaded; + + let href = yield ContentTask.spawn(browser, null, function* () { + let learnMoreLink = content.document.getElementById("learnMoreLink"); + return learnMoreLink.href; + }); + ok(href.endsWith("security-error"), "security-error in the Learn More URL"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +function waitForCertErrorLoad(browser) { + return new Promise(resolve => { + info("Waiting for DOMContentLoaded event"); + browser.addEventListener("DOMContentLoaded", function load() { + browser.removeEventListener("DOMContentLoaded", load, false, true); + resolve(); + }, false, true); + }); +} + +function getCertChain(securityInfoAsString) { + let certChain = ""; + const serhelper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + let securityInfo = serhelper.deserializeObject(securityInfoAsString); + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + let certs = securityInfo.failedCertChain.getEnumerator(); + while (certs.hasMoreElements()) { + let cert = certs.getNext(); + cert.QueryInterface(Ci.nsIX509Cert); + certChain += getPEMString(cert); + } + return certChain; +} + +function getDERString(cert) +{ + var length = {}; + var derArray = cert.getRawDER(length); + var derString = ''; + for (var i = 0; i < derArray.length; i++) { + derString += String.fromCharCode(derArray[i]); + } + return derString; +} + +function getPEMString(cert) +{ + var derb64 = btoa(getDERString(cert)); + // Wrap the Base64 string into lines of 64 characters, + // with CRLF line breaks (as specified in RFC 1421). + var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n"); + return "-----BEGIN CERTIFICATE-----\r\n" + + wrapped + + "\r\n-----END CERTIFICATE-----\r\n"; +} diff --git a/browser/base/content/test/general/browser_aboutHealthReport.js b/browser/base/content/test/general/browser_aboutHealthReport.js new file mode 100644 index 000000000..0be184fb8 --- /dev/null +++ b/browser/base/content/test/general/browser_aboutHealthReport.js @@ -0,0 +1,139 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/"; +const HTTPS_BASE = "https://example.com/browser/browser/base/content/test/general/"; + +const TELEMETRY_LOG_PREF = "toolkit.telemetry.log.level"; +const telemetryOriginalLogPref = Preferences.get(TELEMETRY_LOG_PREF, null); + +const originalReportUrl = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrl"); + +registerCleanupFunction(function() { + // Ensure we don't pollute prefs for next tests. + if (telemetryOriginalLogPref) { + Preferences.set(TELEMETRY_LOG_PREF, telemetryOriginalLogPref); + } else { + Preferences.reset(TELEMETRY_LOG_PREF); + } + + try { + Services.prefs.setCharPref("datareporting.healthreport.about.reportUrl", originalReportUrl); + Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true); + } catch (ex) {} +}); + +function fakeTelemetryNow(...args) { + let date = new Date(...args); + let scope = {}; + const modules = [ + Cu.import("resource://gre/modules/TelemetrySession.jsm", scope), + Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", scope), + Cu.import("resource://gre/modules/TelemetryController.jsm", scope), + ]; + + for (let m of modules) { + m.Policy.now = () => new Date(date); + } + + return date; +} + +function* setupPingArchive() { + let scope = {}; + Cu.import("resource://gre/modules/TelemetryController.jsm", scope); + Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) + .loadSubScript(CHROME_BASE + "healthreport_pingData.js", scope); + + for (let p of scope.TEST_PINGS) { + fakeTelemetryNow(p.date); + p.id = yield scope.TelemetryController.submitExternalPing(p.type, p.payload); + } +} + +var gTests = [ + +{ + desc: "Test the remote commands", + setup: Task.async(function*() + { + Preferences.set(TELEMETRY_LOG_PREF, "Trace"); + yield setupPingArchive(); + Preferences.set("datareporting.healthreport.about.reportUrl", + HTTPS_BASE + "healthreport_testRemoteCommands.html"); + }), + run: function (iframe) + { + let deferred = Promise.defer(); + let results = 0; + try { + iframe.contentWindow.addEventListener("FirefoxHealthReportTestResponse", function evtHandler(event) { + let data = event.detail.data; + if (data.type == "testResult") { + ok(data.pass, data.info); + results++; + } + else if (data.type == "testsComplete") { + is(results, data.count, "Checking number of results received matches the number of tests that should have run"); + iframe.contentWindow.removeEventListener("FirefoxHealthReportTestResponse", evtHandler, true); + deferred.resolve(); + } + }, true); + + } catch (e) { + ok(false, "Failed to get all commands"); + deferred.reject(); + } + return deferred.promise; + } +}, + +]; // gTests + +function test() +{ + waitForExplicitFinish(); + + // xxxmpc leaving this here until we resolve bug 854038 and bug 854060 + requestLongerTimeout(10); + + Task.spawn(function* () { + for (let testCase of gTests) { + info(testCase.desc); + yield testCase.setup(); + + let iframe = yield promiseNewTabLoadEvent("about:healthreport"); + + yield testCase.run(iframe); + + gBrowser.removeCurrentTab(); + } + + finish(); + }); +} + +function promiseNewTabLoadEvent(aUrl, aEventType="load") +{ + let deferred = Promise.defer(); + let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl); + tab.linkedBrowser.addEventListener(aEventType, function load(event) { + tab.linkedBrowser.removeEventListener(aEventType, load, true); + let iframe = tab.linkedBrowser.contentDocument.getElementById("remote-report"); + iframe.addEventListener("load", function frameLoad(e) { + if (iframe.contentWindow.location.href == "about:blank" || + e.target != iframe) { + return; + } + iframe.removeEventListener("load", frameLoad, false); + deferred.resolve(iframe); + }, false); + }, true); + return deferred.promise; +} diff --git a/browser/base/content/test/general/browser_aboutHome.js b/browser/base/content/test/general/browser_aboutHome.js new file mode 100644 index 000000000..f0e19e852 --- /dev/null +++ b/browser/base/content/test/general/browser_aboutHome.js @@ -0,0 +1,668 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This test needs to be split up. See bug 1258717. +requestLongerTimeout(4); +ignoreAllUncaughtExceptions(); + +XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", + "resource:///modules/AboutHome.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); + +const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/" + + "content/test/general/aboutHome_content_script.js"; +var gRightsVersion = Services.prefs.getIntPref("browser.rights.version"); + +registerCleanupFunction(function() { + // Ensure we don't pollute prefs for next tests. + Services.prefs.clearUserPref("network.cookies.cookieBehavior"); + Services.prefs.clearUserPref("network.cookie.lifetimePolicy"); + Services.prefs.clearUserPref("browser.rights.override"); + Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown"); +}); + +add_task(function* () { + info("Check that clearing cookies does not clear storage"); + + yield withSnippetsMap( + () => { + Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService) + .notifyObservers(null, "cookie-changed", "cleared"); + }, + function* () { + isnot(content.gSnippetsMap.get("snippets-last-update"), null, + "snippets-last-update should have a value"); + }); +}); + +add_task(function* () { + info("Check default snippets are shown"); + + yield withSnippetsMap(null, function* () { + let doc = content.document; + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element") + is(snippetsElt.getElementsByTagName("span").length, 1, + "A default snippet is present."); + }); +}); + +add_task(function* () { + info("Check default snippets are shown if snippets are invalid xml"); + + yield withSnippetsMap( + // This must set some incorrect xhtml code. + snippetsMap => snippetsMap.set("snippets", "<p><b></p></b>"), + function* () { + let doc = content.document; + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.getElementsByTagName("span").length, 1, + "A default snippet is present."); + + content.gSnippetsMap.delete("snippets"); + }); +}); + +add_task(function* () { + info("Check that performing a search fires a search event and records to Telemetry."); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + let currEngine = Services.search.currentEngine; + let engine = yield promiseNewEngine("searchSuggestionEngine.xml"); + // Make this actually work in healthreport by giving it an ID: + Object.defineProperty(engine.wrappedJSObject, "identifier", + { value: "org.mozilla.testsearchsuggestions" }); + + let p = promiseContentSearchChange(browser, engine.name); + Services.search.currentEngine = engine; + yield p; + + yield ContentTask.spawn(browser, { expectedName: engine.name }, function* (args) { + let engineName = content.wrappedJSObject.gContentSearchController.defaultEngine.name; + is(engineName, args.expectedName, "Engine name in DOM should match engine we just added"); + }); + + let numSearchesBefore = 0; + // Get the current number of recorded searches. + let histogramKey = engine.identifier + ".abouthome"; + try { + let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot(); + if (histogramKey in hs) { + numSearchesBefore = hs[histogramKey].sum; + } + } catch (ex) { + // No searches performed yet, not a problem, |numSearchesBefore| is 0. + } + + let searchStr = "a search"; + + let expectedURL = Services.search.currentEngine + .getSubmission(searchStr, null, "homepage").uri.spec; + let promise = waitForDocLoadAndStopIt(expectedURL, browser); + + // Perform a search to increase the SEARCH_COUNT histogram. + yield ContentTask.spawn(browser, { searchStr }, function* (args) { + let doc = content.document; + info("Perform a search."); + doc.getElementById("searchText").value = args.searchStr; + doc.getElementById("searchSubmit").click(); + }); + + yield promise; + + // Make sure the SEARCH_COUNTS histogram has the right key and count. + let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot(); + Assert.ok(histogramKey in hs, "histogram with key should be recorded"); + Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1, + "histogram sum should be incremented"); + + Services.search.currentEngine = currEngine; + try { + Services.search.removeEngine(engine); + } catch (ex) {} + }); +}); + +add_task(function* () { + info("Check snippets map is cleared if cached version is old"); + + yield withSnippetsMap( + snippetsMap => { + snippetsMap.set("snippets", "test"); + snippetsMap.set("snippets-cached-version", 0); + }, + function* () { + let snippetsMap = content.gSnippetsMap; + ok(!snippetsMap.has("snippets"), "snippets have been properly cleared"); + ok(!snippetsMap.has("snippets-cached-version"), + "cached-version has been properly cleared"); + }); +}); + +add_task(function* () { + info("Check cached snippets are shown if cached version is current"); + + yield withSnippetsMap( + snippetsMap => snippetsMap.set("snippets", "test"), + function* (args) { + let doc = content.document; + let snippetsMap = content.gSnippetsMap + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.innerHTML, "test", "Cached snippet is present."); + + is(snippetsMap.get("snippets"), "test", "snippets still cached"); + is(snippetsMap.get("snippets-cached-version"), + args.expectedVersion, + "cached-version is correct"); + ok(snippetsMap.has("snippets-last-update"), "last-update still exists"); + }, { expectedVersion: AboutHomeUtils.snippetsVersion }); +}); + +add_task(function* () { + info("Check if the 'Know Your Rights' default snippet is shown when " + + "'browser.rights.override' pref is set and that its link works"); + + Services.prefs.setBoolPref("browser.rights.override", false); + + ok(AboutHomeUtils.showKnowYourRights, "AboutHomeUtils.showKnowYourRights should be TRUE"); + + yield withSnippetsMap(null, function* () { + let doc = content.document; + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + let linkEl = snippetsElt.querySelector("a"); + is(linkEl.href, "about:rights", "Snippet link is present."); + }, null, function* () { + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:rights"); + yield BrowserTestUtils.synthesizeMouseAtCenter("a[href='about:rights']", { + button: 0 + }, gBrowser.selectedBrowser); + yield loadPromise; + is(gBrowser.currentURI.spec, "about:rights", "about:rights should have opened."); + }); + + + Services.prefs.clearUserPref("browser.rights.override"); +}); + +add_task(function* () { + info("Check if the 'Know Your Rights' default snippet is NOT shown when " + + "'browser.rights.override' pref is NOT set"); + + Services.prefs.setBoolPref("browser.rights.override", true); + + let rightsData = AboutHomeUtils.knowYourRightsData; + ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE"); + + yield withSnippetsMap(null, function*() { + let doc = content.document; + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", + "Snippet link should not point to about:rights."); + }); + + Services.prefs.clearUserPref("browser.rights.override"); +}); + +add_task(function* () { + info("Check POST search engine support"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + return new Promise(resolve => { + let searchObserver = Task.async(function* search_observer(subject, topic, data) { + let currEngine = Services.search.defaultEngine; + let engine = subject.QueryInterface(Ci.nsISearchEngine); + info("Observer: " + data + " for " + engine.name); + + if (data != "engine-added") + return; + + if (engine.name != "POST Search") + return; + + Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); + + // Ready to execute the tests! + let needle = "Search for something awesome."; + + let p = promiseContentSearchChange(browser, engine.name); + Services.search.defaultEngine = engine; + yield p; + + let promise = BrowserTestUtils.browserLoaded(browser); + + yield ContentTask.spawn(browser, { needle }, function* (args) { + let doc = content.document; + doc.getElementById("searchText").value = args.needle; + doc.getElementById("searchSubmit").click(); + }); + + yield promise; + + // When the search results load, check them for correctness. + yield ContentTask.spawn(browser, { needle }, function* (args) { + let loadedText = content.document.body.textContent; + ok(loadedText, "search page loaded"); + is(loadedText, "searchterms=" + escape(args.needle.replace(/\s/g, "+")), + "Search text should arrive correctly"); + }); + + Services.search.defaultEngine = currEngine; + try { + Services.search.removeEngine(engine); + } catch (ex) {} + resolve(); + }); + Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); + Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml", + null, null, false); + }); + }); +}); + +add_task(function* () { + info("Make sure that a page can't imitate about:home"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + let promise = BrowserTestUtils.browserLoaded(browser); + browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html"); + yield promise; + + yield ContentTask.spawn(browser, null, function* () { + let button = content.document.getElementById("settings"); + ok(button, "Found settings button in test page"); + button.click(); + }); + + yield new Promise(resolve => { + // It may take a few turns of the event loop before the window + // is displayed, so we wait. + function check(n) { + let win = Services.wm.getMostRecentWindow("Browser:Preferences"); + ok(!win, "Preferences window not showing"); + if (win) { + win.close(); + } + + if (n > 0) { + executeSoon(() => check(n-1)); + } else { + resolve(); + } + } + + check(5); + }); + }); +}); + +add_task(function* () { + // See browser_contentSearchUI.js for comprehensive content search UI tests. + info("Search suggestion smoke test"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + // Add a test engine that provides suggestions and switch to it. + let currEngine = Services.search.currentEngine; + let engine = yield promiseNewEngine("searchSuggestionEngine.xml"); + let p = promiseContentSearchChange(browser, engine.name); + Services.search.currentEngine = engine; + yield p; + + yield ContentTask.spawn(browser, null, function* () { + // Avoid intermittent failures. + content.wrappedJSObject.gContentSearchController.remoteTimeout = 5000; + + // Type an X in the search input. + let input = content.document.getElementById("searchText"); + input.focus(); + }); + + yield BrowserTestUtils.synthesizeKey("x", {}, browser); + + yield ContentTask.spawn(browser, null, function* () { + // Wait for the search suggestions to become visible. + let table = content.document.getElementById("searchSuggestionTable"); + let input = content.document.getElementById("searchText"); + + yield new Promise(resolve => { + let observer = new content.MutationObserver(() => { + if (input.getAttribute("aria-expanded") == "true") { + observer.disconnect(); + ok(!table.hidden, "Search suggestion table unhidden"); + resolve(); + } + }); + observer.observe(input, { + attributes: true, + attributeFilter: ["aria-expanded"], + }); + }); + }); + + // Empty the search input, causing the suggestions to be hidden. + yield BrowserTestUtils.synthesizeKey("a", { accelKey: true }, browser); + yield BrowserTestUtils.synthesizeKey("VK_DELETE", {}, browser); + + yield ContentTask.spawn(browser, null, function* () { + let table = content.document.getElementById("searchSuggestionTable"); + yield ContentTaskUtils.waitForCondition(() => table.hidden, + "Search suggestion table hidden"); + }); + + Services.search.currentEngine = currEngine; + try { + Services.search.removeEngine(engine); + } catch (ex) { } + }); +}); + +add_task(function* () { + info("Clicking suggestion list while composing"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + // Add a test engine that provides suggestions and switch to it. + let currEngine = Services.search.currentEngine; + let engine = yield promiseNewEngine("searchSuggestionEngine.xml"); + let p = promiseContentSearchChange(browser, engine.name); + Services.search.currentEngine = engine; + yield p; + + yield ContentTask.spawn(browser, null, function* () { + // Start composition and type "x" + let input = content.document.getElementById("searchText"); + input.focus(); + }); + + yield BrowserTestUtils.synthesizeComposition({ + type: "compositionstart", + data: "" + }, browser); + yield BrowserTestUtils.synthesizeCompositionChange({ + composition: { + string: "x", + clauses: [ + { length: 1, attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE } + ] + }, + caret: { start: 1, length: 0 } + }, browser); + + yield ContentTask.spawn(browser, null, function* () { + let searchController = content.wrappedJSObject.gContentSearchController; + + // Wait for the search suggestions to become visible. + let table = searchController._suggestionsList; + let input = content.document.getElementById("searchText"); + + yield new Promise(resolve => { + let observer = new content.MutationObserver(() => { + if (input.getAttribute("aria-expanded") == "true") { + observer.disconnect(); + ok(!table.hidden, "Search suggestion table unhidden"); + resolve(); + } + }); + observer.observe(input, { + attributes: true, + attributeFilter: ["aria-expanded"], + }); + }); + + let row = table.children[1]; + row.setAttribute("id", "TEMPID"); + + // ContentSearchUIController looks at the current selectedIndex when + // performing a search. Synthesizing the mouse event on the suggestion + // doesn't actually mouseover the suggestion and trigger it to be flagged + // as selected, so we manually select it first. + searchController.selectedIndex = 1; + }); + + // Click the second suggestion. + let expectedURL = Services.search.currentEngine + .getSubmission("xbar", null, "homepage").uri.spec; + let loadPromise = waitForDocLoadAndStopIt(expectedURL); + yield BrowserTestUtils.synthesizeMouseAtCenter("#TEMPID", { + button: 0 + }, browser); + yield loadPromise; + + yield ContentTask.spawn(browser, null, function* () { + let input = content.document.getElementById("searchText"); + ok(input.value == "x", "Input value did not change"); + + let row = content.document.getElementById("TEMPID"); + if (row) { + row.removeAttribute("id"); + } + }); + + Services.search.currentEngine = currEngine; + try { + Services.search.removeEngine(engine); + } catch (ex) { } + }); +}); + +add_task(function* () { + info("Pressing any key should focus the search box in the page, and send the key to it"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser); + + yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + isnot(doc.getElementById("searchText"), doc.activeElement, + "Search input should not be the active element."); + }); + + yield BrowserTestUtils.synthesizeKey("a", {}, browser); + + yield ContentTask.spawn(browser, null, function* () { + let doc = content.document; + let searchInput = doc.getElementById("searchText"); + yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput, + "Search input should be the active element."); + is(searchInput.value, "a", "Search input should be 'a'."); + }); + }); +}); + +add_task(function* () { + info("Cmd+k should focus the search box in the toolbar when it's present"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser); + + let doc = window.document; + let searchInput = doc.getElementById("searchbar").textbox.inputField; + isnot(searchInput, doc.activeElement, "Search bar should not be the active element."); + + EventUtils.synthesizeKey("k", { accelKey: true }); + yield promiseWaitForCondition(() => doc.activeElement === searchInput); + is(searchInput, doc.activeElement, "Search bar should be the active element."); + }); +}); + +add_task(function* () { + info("Sync button should open about:preferences#sync"); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + let oldOpenPrefs = window.openPreferences; + let openPrefsPromise = new Promise(resolve => { + window.openPreferences = function (pane, params) { + resolve({ pane: pane, params: params }); + }; + }); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#sync", {}, browser); + + let result = yield openPrefsPromise; + window.openPreferences = oldOpenPrefs; + + is(result.pane, "paneSync", "openPreferences should be called with paneSync"); + is(result.params.urlParams.entrypoint, "abouthome", + "openPreferences should be called with abouthome entrypoint"); + }); +}); + +add_task(function* () { + info("Pressing Space while the Addons button is focused should activate it"); + + // Skip this test on Mac, because Space doesn't activate the button there. + if (AppConstants.platform == "macosx") { + return; + } + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { + info("Waiting for about:addons tab to open..."); + let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons"); + + yield ContentTask.spawn(browser, null, function* () { + let addOnsButton = content.document.getElementById("addons"); + addOnsButton.focus(); + }); + yield BrowserTestUtils.synthesizeKey(" ", {}, browser); + + let tab = yield promiseTabOpened; + is(tab.linkedBrowser.currentURI.spec, "about:addons", + "Should have seen the about:addons tab"); + yield BrowserTestUtils.removeTab(tab); + }); +}); + +/** + * Cleans up snippets and ensures that by default we don't try to check for + * remote snippets since that may cause network bustage or slowness. + * + * @param aSetupFn + * The setup function to be run. + * @param testFn + * the content task to run + * @param testArgs (optional) + * the parameters to pass to the content task + * @param parentFn (optional) + * the function to run in the parent after the content task has completed. + * @return {Promise} resolved when the snippets are ready. Gets the snippets map. + */ +function* withSnippetsMap(setupFn, testFn, testArgs = null, parentFn = null) { + let setupFnSource; + if (setupFn) { + setupFnSource = setupFn.toSource(); + } + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) { + let promiseAfterLocationChange = () => { + return ContentTask.spawn(browser, { + setupFnSource, + version: AboutHomeUtils.snippetsVersion, + }, function* (args) { + return new Promise(resolve => { + let document = content.document; + // We're not using Promise-based listeners, because they resolve asynchronously. + // The snippets test setup code relies on synchronous behaviour here. + document.addEventListener("AboutHomeLoadSnippets", function loadSnippets() { + document.removeEventListener("AboutHomeLoadSnippets", loadSnippets); + + let updateSnippets; + if (args.setupFnSource) { + updateSnippets = eval(`(() => (${args.setupFnSource}))()`); + } + + content.wrappedJSObject.ensureSnippetsMapThen(snippetsMap => { + snippetsMap = Cu.waiveXrays(snippetsMap); + info("Got snippets map: " + + "{ last-update: " + snippetsMap.get("snippets-last-update") + + ", cached-version: " + snippetsMap.get("snippets-cached-version") + + " }"); + // Don't try to update. + snippetsMap.set("snippets-last-update", Date.now()); + snippetsMap.set("snippets-cached-version", args.version); + // Clear snippets. + snippetsMap.delete("snippets"); + + if (updateSnippets) { + updateSnippets(snippetsMap); + } + + // Tack it to the global object + content.gSnippetsMap = snippetsMap; + + resolve(); + }); + }); + }); + }); + }; + + // We'd like to listen to the 'AboutHomeLoadSnippets' event on a fresh + // document as soon as technically possible, so we use webProgress. + let promise = new Promise(resolve => { + let wpl = { + onLocationChange() { + gBrowser.removeProgressListener(wpl); + // Phase 2: retrieving the snippets map is the next promise on our agenda. + promiseAfterLocationChange().then(resolve); + }, + onProgressChange() {}, + onStatusChange() {}, + onSecurityChange() {} + }; + gBrowser.addProgressListener(wpl); + }); + + // Set the URL to 'about:home' here to allow capturing the 'AboutHomeLoadSnippets' + // event. + browser.loadURI("about:home"); + // Wait for LocationChange. + yield promise; + + yield ContentTask.spawn(browser, testArgs, testFn); + if (parentFn) { + yield parentFn(); + } + }); +} + +function promiseContentSearchChange(browser, newEngineName) { + return ContentTask.spawn(browser, { newEngineName }, function* (args) { + return new Promise(resolve => { + content.addEventListener("ContentSearchService", function listener(aEvent) { + if (aEvent.detail.type == "CurrentState" && + content.wrappedJSObject.gContentSearchController.defaultEngine.name == args.newEngineName) { + content.removeEventListener("ContentSearchService", listener); + resolve(); + } + }); + }); + }); +} + +function promiseNewEngine(basename) { + info("Waiting for engine to be added: " + basename); + return new Promise((resolve, reject) => { + let url = getRootDirectory(gTestPath) + basename; + Services.search.addEngine(url, null, "", false, { + onSuccess: function (engine) { + info("Search engine added: " + basename); + registerCleanupFunction(() => { + try { + Services.search.removeEngine(engine); + } catch (ex) { /* Can't remove the engine more than once */ } + }); + resolve(engine); + }, + onError: function (errCode) { + ok(false, "addEngine failed with error code " + errCode); + reject(); + }, + }); + }); +} diff --git a/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js b/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js new file mode 100644 index 000000000..bfe0fe9c8 --- /dev/null +++ b/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js @@ -0,0 +1,28 @@ +add_task(function* () { + let newWindow = yield BrowserTestUtils.openNewBrowserWindow(); + + let resizedPromise = BrowserTestUtils.waitForEvent(newWindow, "resize"); + newWindow.resizeTo(300, 300); + yield resizedPromise; + + yield BrowserTestUtils.openNewForegroundTab(newWindow.gBrowser, "about:home"); + + yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () { + Assert.equal(content.document.body.getAttribute("narrow"), "true", "narrow mode"); + }); + + resizedPromise = BrowserTestUtils.waitForContentEvent(newWindow.gBrowser.selectedBrowser, "resize"); + + + yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () { + content.window.resizeTo(800, 800); + }); + + yield resizedPromise; + + yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () { + Assert.equal(content.document.body.hasAttribute("narrow"), false, "non-narrow mode"); + }); + + yield BrowserTestUtils.closeWindow(newWindow); +}); diff --git a/browser/base/content/test/general/browser_aboutNetError.js b/browser/base/content/test/general/browser_aboutNetError.js new file mode 100644 index 000000000..5185cbcaa --- /dev/null +++ b/browser/base/content/test/general/browser_aboutNetError.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Set ourselves up for TLS error +Services.prefs.setIntPref("security.tls.version.max", 3); +Services.prefs.setIntPref("security.tls.version.min", 3); + +const LOW_TLS_VERSION = "https://tls1.example.com/"; +const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); +const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + +add_task(function* checkReturnToPreviousPage() { + info("Loading a TLS page that isn't supported, ensure we have a fix button and clicking it then loads the page"); + let browser; + let pageLoaded; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + gBrowser.selectedTab = gBrowser.addTab(LOW_TLS_VERSION); + browser = gBrowser.selectedBrowser; + pageLoaded = BrowserTestUtils.waitForErrorPage(browser); + }, false); + + info("Loading and waiting for the net error"); + yield pageLoaded; + + // NB: This code assumes that the error page and the test page load in the + // same process. If this test starts to fail, it could be because they load + // in different processes. + yield ContentTask.spawn(browser, LOW_TLS_VERSION, function* (LOW_TLS_VERSION_) { + ok(content.document.getElementById("prefResetButton").getBoundingClientRect().left >= 0, + "Should have a visible button"); + + ok(content.document.documentURI.startsWith("about:neterror"), "Should be showing error page"); + + let doc = content.document; + let prefResetButton = doc.getElementById("prefResetButton"); + is(prefResetButton.getAttribute("autofocus"), "true", "prefResetButton has autofocus"); + prefResetButton.click(); + + yield ContentTaskUtils.waitForEvent(this, "pageshow", true); + + is(content.document.documentURI, LOW_TLS_VERSION_, "Should not be showing page"); + }); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js b/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js new file mode 100644 index 000000000..e574ba978 --- /dev/null +++ b/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: window.location is null"); + + +add_task(function* checkIdentityOfAboutSupport() { + let tab = gBrowser.loadOneTab("about:support", { + referrerURI: null, + inBackground: false, + allowThirdPartyFixup: false, + relatedToCurrent: false, + skipAnimation: true, + allowMixedContent: false + }); + + yield promiseTabLoaded(tab); + let identityBox = document.getElementById("identity-box"); + is(identityBox.className, "chromeUI", "Should know that we're chrome."); + gBrowser.removeTab(tab); +}); + diff --git a/browser/base/content/test/general/browser_accesskeys.js b/browser/base/content/test/general/browser_accesskeys.js new file mode 100644 index 000000000..56fe3995f --- /dev/null +++ b/browser/base/content/test/general/browser_accesskeys.js @@ -0,0 +1,82 @@ +add_task(function *() { + yield pushPrefs(["ui.key.contentAccess", 5], ["ui.key.chromeAccess", 5]); + + const gPageURL1 = "data:text/html,<body><p>" + + "<button id='button' accesskey='y'>Button</button>" + + "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" + + "</p></body>"; + let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1); + tab1.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false); + + Services.focus.clearFocus(window); + + // Press an accesskey in the child document while the chrome is focused. + let focusedId = yield performAccessKey("y"); + is(focusedId, "button", "button accesskey"); + + // Press an accesskey in the child document while the content document is focused. + focusedId = yield performAccessKey("z"); + is(focusedId, "checkbox", "checkbox accesskey"); + + // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused. + let newButton = document.createElement("button"); + newButton.id = "chromebutton"; + newButton.setAttribute("accesskey", "z"); + document.documentElement.appendChild(newButton); + + Services.focus.clearFocus(window); + + focusedId = yield performAccessKeyForChrome("z"); + is(focusedId, "chromebutton", "chromebutton accesskey"); + + // Add a second tab and ensure that accesskey from the first tab is not used. + const gPageURL2 = "data:text/html,<body>" + + "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" + + "</body>"; + let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2); + tab2.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false); + + Services.focus.clearFocus(window); + + focusedId = yield performAccessKey("y"); + is(focusedId, "tab2button", "button accesskey in tab2"); + + // Press the accesskey for the chrome element while the content document is focused. + focusedId = yield performAccessKeyForChrome("z"); + is(focusedId, "chromebutton", "chromebutton accesskey"); + + newButton.parentNode.removeChild(newButton); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); +}); + +function childHandleFocus() { + content.document.body.firstChild.addEventListener("focus", function focused(event) { + let focusedElement = content.document.activeElement; + focusedElement.blur(); + sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id }) + }, true); +} + +function performAccessKey(key) +{ + return new Promise(resolve => { + let mm = gBrowser.selectedBrowser.messageManager; + mm.addMessageListener("Test:FocusFromAccessKey", function listenForFocus(msg) { + mm.removeMessageListener("Test:FocusFromAccessKey", listenForFocus); + resolve(msg.data.focus); + }); + + EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true }); + }); +} + +// This version is used when a chrome elemnt is expected to be found for an accesskey. +function* performAccessKeyForChrome(key, inChild) +{ + let waitFocusChangePromise = BrowserTestUtils.waitForEvent(document, "focus", true); + EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true }); + yield waitFocusChangePromise; + return document.activeElement.id; +} diff --git a/browser/base/content/test/general/browser_addCertException.js b/browser/base/content/test/general/browser_addCertException.js new file mode 100644 index 000000000..e2cf34b47 --- /dev/null +++ b/browser/base/content/test/general/browser_addCertException.js @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test adding a certificate exception by attempting to browse to a site with +// a bad certificate, being redirected to the internal about:certerror page, +// using the button contained therein to load the certificate exception +// dialog, using that to add an exception, and finally successfully visiting +// the site, including showing the right identity box and control center icons. +add_task(function* () { + yield BrowserTestUtils.openNewForegroundTab(gBrowser); + yield loadBadCertPage("https://expired.example.com"); + checkControlPanelIcons(); + let certOverrideService = Cc["@mozilla.org/security/certoverride;1"] + .getService(Ci.nsICertOverrideService); + certOverrideService.clearValidityOverride("expired.example.com", -1); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Check for the correct icons in the identity box and control center. +function checkControlPanelIcons() { + let { gIdentityHandler } = gBrowser.ownerGlobal; + gIdentityHandler._identityBox.click(); + document.getElementById("identity-popup-security-expander").click(); + + is_element_visible(document.getElementById("connection-icon"), "Should see connection icon"); + let connectionIconImage = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("connection-icon"), "") + .getPropertyValue("list-style-image"); + let securityViewBG = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-popup-securityView"), "") + .getPropertyValue("background-image"); + let securityContentBG = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-popup-security-content"), "") + .getPropertyValue("background-image"); + is(connectionIconImage, + "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")", + "Using expected icon image in the identity block"); + is(securityViewBG, + "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")", + "Using expected icon image in the Control Center main view"); + is(securityContentBG, + "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")", + "Using expected icon image in the Control Center subview"); + + gIdentityHandler._identityPopup.hidden = true; +} + diff --git a/browser/base/content/test/general/browser_addKeywordSearch.js b/browser/base/content/test/general/browser_addKeywordSearch.js new file mode 100644 index 000000000..f38050b43 --- /dev/null +++ b/browser/base/content/test/general/browser_addKeywordSearch.js @@ -0,0 +1,81 @@ +var testData = [ + { desc: "No path", + action: "http://example.com/", + param: "q", + }, + { desc: "With path", + action: "http://example.com/new-path-here/", + param: "q", + }, + { desc: "No action", + action: "", + param: "q", + }, + { desc: "With Query String", + action: "http://example.com/search?oe=utf-8", + param: "q", + }, +]; + +add_task(function*() { + const TEST_URL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let count = 0; + for (let method of ["GET", "POST"]) { + for (let {desc, action, param } of testData) { + info(`Running ${method} keyword test '${desc}'`); + let id = `keyword-form-${count++}`; + let contextMenu = document.getElementById("contentAreaContextMenu"); + let contextMenuPromise = + BrowserTestUtils.waitForEvent(contextMenu, "popupshown") + .then(() => gContextMenuContentData.popupNode); + + yield ContentTask.spawn(tab.linkedBrowser, + { action, param, method, id }, function* (args) { + let doc = content.document; + let form = doc.createElement("form"); + form.id = args.id; + form.method = args.method; + form.action = args.action; + let element = doc.createElement("input"); + element.setAttribute("type", "text"); + element.setAttribute("name", args.param); + form.appendChild(element); + doc.body.appendChild(form); + }); + + yield BrowserTestUtils.synthesizeMouseAtCenter(`#${id} > input`, + { type : "contextmenu", button : 2 }, + tab.linkedBrowser); + let target = yield contextMenuPromise; + + yield new Promise(resolve => { + let url = action || tab.linkedBrowser.currentURI.spec; + let mm = tab.linkedBrowser.messageManager; + let onMessage = (message) => { + mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage); + if (method == "GET") { + ok(message.data.spec.endsWith(`${param}=%s`), + `Check expected url for field named ${param} and action ${action}`); + } else { + is(message.data.spec, url, + `Check expected url for field named ${param} and action ${action}`); + is(message.data.postData, `${param}%3D%25s`, + `Check expected POST data for field named ${param} and action ${action}`); + } + resolve(); + }; + mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage); + + mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", null, { target }); + }); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + contextMenu.hidePopup(); + yield popupHiddenPromise; + } + } + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_alltabslistener.js b/browser/base/content/test/general/browser_alltabslistener.js new file mode 100644 index 000000000..a56473ec9 --- /dev/null +++ b/browser/base/content/test/general/browser_alltabslistener.js @@ -0,0 +1,206 @@ +var Ci = Components.interfaces; + +const gCompleteState = Ci.nsIWebProgressListener.STATE_STOP + + Ci.nsIWebProgressListener.STATE_IS_NETWORK; + +var gFrontProgressListener = { + onProgressChange: function (aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) { + }, + + onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) { + var state = "onStateChange"; + info("FrontProgress: " + state + " 0x" + aStateFlags.toString(16)); + ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener"); + is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener"); + gFrontNotificationsPos++; + }, + + onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) { + var state = "onLocationChange"; + info("FrontProgress: " + state + " " + aLocationURI.spec); + ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener"); + is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener"); + gFrontNotificationsPos++; + }, + + onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { + }, + + onSecurityChange: function (aWebProgress, aRequest, aState) { + var state = "onSecurityChange"; + info("FrontProgress: " + state + " 0x" + aState.toString(16)); + ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener"); + is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener"); + gFrontNotificationsPos++; + } +} + +var gAllProgressListener = { + onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + var state = "onStateChange"; + info("AllProgress: " + state + " 0x" + aStateFlags.toString(16)); + ok(aBrowser == gTestBrowser, state + " notification came from the correct browser"); + ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener"); + is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener"); + gAllNotificationsPos++; + + if ((aStateFlags & gCompleteState) == gCompleteState) { + ok(gAllNotificationsPos == gAllNotifications.length, "Saw the expected number of notifications"); + ok(gFrontNotificationsPos == gFrontNotifications.length, "Saw the expected number of frontnotifications"); + executeSoon(gNextTest); + } + }, + + onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI, + aFlags) { + var state = "onLocationChange"; + info("AllProgress: " + state + " " + aLocationURI.spec); + ok(aBrowser == gTestBrowser, state + " notification came from the correct browser"); + ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener"); + is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener"); + gAllNotificationsPos++; + }, + + onStatusChange: function (aBrowser, aWebProgress, aRequest, aStatus, aMessage) { + var state = "onStatusChange"; + ok(aBrowser == gTestBrowser, state + " notification came from the correct browser"); + }, + + onSecurityChange: function (aBrowser, aWebProgress, aRequest, aState) { + var state = "onSecurityChange"; + info("AllProgress: " + state + " 0x" + aState.toString(16)); + ok(aBrowser == gTestBrowser, state + " notification came from the correct browser"); + ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener"); + is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener"); + gAllNotificationsPos++; + } +} + +var gFrontNotifications, gAllNotifications, gFrontNotificationsPos, gAllNotificationsPos; +var gBackgroundTab, gForegroundTab, gBackgroundBrowser, gForegroundBrowser, gTestBrowser; +var gTestPage = "/browser/browser/base/content/test/general/alltabslistener.html"; +const kBasePage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; +var gNextTest; + +function test() { + waitForExplicitFinish(); + + gBackgroundTab = gBrowser.addTab(); + gForegroundTab = gBrowser.addTab(); + gBackgroundBrowser = gBrowser.getBrowserForTab(gBackgroundTab); + gForegroundBrowser = gBrowser.getBrowserForTab(gForegroundTab); + gBrowser.selectedTab = gForegroundTab; + + // We must wait until a page has completed loading before + // starting tests or we get notifications from that + let promises = [ + waitForDocLoadComplete(gBackgroundBrowser), + waitForDocLoadComplete(gForegroundBrowser) + ]; + gBackgroundBrowser.loadURI(kBasePage); + gForegroundBrowser.loadURI(kBasePage); + Promise.all(promises).then(startTest1); +} + +function runTest(browser, url, next) { + gFrontNotificationsPos = 0; + gAllNotificationsPos = 0; + gNextTest = next; + gTestBrowser = browser; + browser.loadURI(url); +} + +function startTest1() { + info("\nTest 1"); + gBrowser.addProgressListener(gFrontProgressListener); + gBrowser.addTabsProgressListener(gAllProgressListener); + + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onStateChange" + ]; + gFrontNotifications = gAllNotifications; + runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2); +} + +function startTest2() { + info("\nTest 2"); + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onSecurityChange", + "onStateChange" + ]; + gFrontNotifications = gAllNotifications; + runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3); +} + +function startTest3() { + info("\nTest 3"); + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onStateChange" + ]; + gFrontNotifications = []; + runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4); +} + +function startTest4() { + info("\nTest 4"); + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onSecurityChange", + "onStateChange" + ]; + gFrontNotifications = []; + runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5); +} + +function startTest5() { + info("\nTest 5"); + // Switch the foreground browser + [gForegroundBrowser, gBackgroundBrowser] = [gBackgroundBrowser, gForegroundBrowser]; + [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab]; + // Avoid the onLocationChange this will fire + gBrowser.removeProgressListener(gFrontProgressListener); + gBrowser.selectedTab = gForegroundTab; + gBrowser.addProgressListener(gFrontProgressListener); + + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onStateChange" + ]; + gFrontNotifications = gAllNotifications; + runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6); +} + +function startTest6() { + info("\nTest 6"); + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onStateChange" + ]; + gFrontNotifications = []; + runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest); +} + +function finishTest() { + gBrowser.removeProgressListener(gFrontProgressListener); + gBrowser.removeTabsProgressListener(gAllProgressListener); + gBrowser.removeTab(gBackgroundTab); + gBrowser.removeTab(gForegroundTab); + finish(); +} diff --git a/browser/base/content/test/general/browser_audioTabIcon.js b/browser/base/content/test/general/browser_audioTabIcon.js new file mode 100644 index 000000000..4d7a7bbd8 --- /dev/null +++ b/browser/base/content/test/general/browser_audioTabIcon.js @@ -0,0 +1,504 @@ +const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html"; +const TABATTR_REMOVAL_PREFNAME = "browser.tabs.delayHidingAudioPlayingIconMS"; +const INITIAL_TABATTR_REMOVAL_DELAY_MS = Services.prefs.getIntPref(TABATTR_REMOVAL_PREFNAME); + +function* wait_for_tab_playing_event(tab, expectPlaying) { + if (tab.soundPlaying == expectPlaying) { + ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } + return yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => { + if (event.detail.changed.includes("soundplaying")) { + is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } + return false; + }); +} + +function* play(tab) { + let browser = tab.linkedBrowser; + yield ContentTask.spawn(browser, {}, function* () { + let audio = content.document.querySelector("audio"); + audio.play(); + }); + + yield wait_for_tab_playing_event(tab, true); +} + +function* pause(tab, options) { + ok(tab.hasAttribute("soundplaying"), "The tab should have the soundplaying attribute when pause() is called"); + + let extendedDelay = options && options.extendedDelay; + if (extendedDelay) { + // Use 10s to remove possibility of race condition with attr removal. + Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, 10000); + } + + try { + let browser = tab.linkedBrowser; + let awaitDOMAudioPlaybackStopped = + BrowserTestUtils.waitForEvent(browser, "DOMAudioPlaybackStopped", "DOMAudioPlaybackStopped event should get fired after pause"); + let awaitTabPausedAttrModified = + wait_for_tab_playing_event(tab, false); + yield ContentTask.spawn(browser, {}, function* () { + let audio = content.document.querySelector("audio"); + audio.pause(); + }); + + if (extendedDelay) { + ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after pausing"); + + yield awaitDOMAudioPlaybackStopped; + ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped"); + } + + yield awaitTabPausedAttrModified; + ok(!tab.hasAttribute("soundplaying"), "The tab should not have the soundplaying attribute after the timeout has resolved"); + } finally { + // Make sure other tests don't timeout if an exception gets thrown above. + // Need to use setIntPref instead of clearUserPref because prefs_general.js + // overrides the default value to help this and other tests run faster. + Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, INITIAL_TABATTR_REMOVAL_DELAY_MS); + } +} + +function disable_non_test_mouse(disable) { + let utils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + utils.disableNonTestMouseEvents(disable); +} + +function* hover_icon(icon, tooltip) { + disable_non_test_mouse(true); + + let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown"); + EventUtils.synthesizeMouse(icon, 1, 1, {type: "mouseover"}); + EventUtils.synthesizeMouse(icon, 2, 2, {type: "mousemove"}); + EventUtils.synthesizeMouse(icon, 3, 3, {type: "mousemove"}); + EventUtils.synthesizeMouse(icon, 4, 4, {type: "mousemove"}); + return popupShownPromise; +} + +function leave_icon(icon) { + EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"}); + EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); + EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); + EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); + + disable_non_test_mouse(false); +} + +function* test_tooltip(icon, expectedTooltip, isActiveTab) { + let tooltip = document.getElementById("tabbrowser-tab-tooltip"); + + yield hover_icon(icon, tooltip); + if (isActiveTab) { + // The active tab should have the keybinding shortcut in the tooltip. + // We check this by ensuring that the strings are not equal but the expected + // message appears in the beginning. + isnot(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal"); + is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected"); + } else { + is(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal"); + } + leave_icon(icon); +} + +// The set of tabs which have ever had their mute state changed. +// Used to determine whether the tab should have a muteReason value. +let everMutedTabs = new WeakSet(); + +function get_wait_for_mute_promise(tab, expectMuted) { + return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => { + if (event.detail.changed.includes("muted")) { + is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted"); + is(tab.muted, expectMuted, "The tab muted property " + (expectMuted ? "" : "not ") + "be true"); + + if (expectMuted || everMutedTabs.has(tab)) { + everMutedTabs.add(tab); + is(tab.muteReason, null, "The tab should have a null muteReason value"); + } else { + is(tab.muteReason, undefined, "The tab should have an undefined muteReason value"); + } + return true; + } + return false; + }); +} + +function* test_mute_tab(tab, icon, expectMuted) { + let mutedPromise = test_mute_keybinding(tab, expectMuted); + + let activeTab = gBrowser.selectedTab; + + let tooltip = document.getElementById("tabbrowser-tab-tooltip"); + + yield hover_icon(icon, tooltip); + EventUtils.synthesizeMouseAtCenter(icon, {button: 0}); + leave_icon(icon); + + is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab"); + + return mutedPromise; +} + +function get_tab_state(tab) { + const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + return JSON.parse(ss.getTabState(tab)); +} + +function* test_muting_using_menu(tab, expectMuted) { + // Show the popup menu + let contextMenu = document.getElementById("tabContextMenu"); + let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(tab, {type: "contextmenu", button: 2}); + yield popupShownPromise; + + // Check the menu + let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab"; + let toggleMute = document.getElementById("context_toggleMuteTab"); + is(toggleMute.label, expectedLabel, "Correct label expected"); + is(toggleMute.accessKey, "M", "Correct accessKey expected"); + + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute"); + + yield play(tab); + + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute"); + + yield pause(tab); + + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute"); + + // Click on the menu and wait for the tab to be muted. + let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(toggleMute, {}); + yield popupHiddenPromise; + yield mutedPromise; +} + +function* test_playing_icon_on_tab(tab, browser, isPinned) { + let icon = document.getAnonymousElementByAttribute(tab, "anonid", + isPinned ? "overlay-icon" : "soundplaying-icon"); + let isActiveTab = tab === gBrowser.selectedTab; + + yield play(tab); + + yield test_tooltip(icon, "Mute tab", isActiveTab); + + ok(!("muted" in get_tab_state(tab)), "No muted attribute should be persisted"); + ok(!("muteReason" in get_tab_state(tab)), "No muteReason property should be persisted"); + + yield test_mute_tab(tab, icon, true); + + ok("muted" in get_tab_state(tab), "Muted attribute should be persisted"); + ok("muteReason" in get_tab_state(tab), "muteReason property should be persisted"); + + yield test_tooltip(icon, "Unmute tab", isActiveTab); + + yield test_mute_tab(tab, icon, false); + + ok(!("muted" in get_tab_state(tab)), "No muted attribute should be persisted"); + ok(!("muteReason" in get_tab_state(tab)), "No muteReason property should be persisted"); + + yield test_tooltip(icon, "Mute tab", isActiveTab); + + yield test_mute_tab(tab, icon, true); + + yield pause(tab); + + ok(tab.hasAttribute("muted") && + !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing"); + ok(tab.muted && !tab.soundPlaying, "Tab should still be muted but not playing"); + + yield test_tooltip(icon, "Unmute tab", isActiveTab); + + yield test_mute_tab(tab, icon, false); + + ok(!tab.hasAttribute("muted") && + !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing"); + ok(!tab.muted && !tab.soundPlaying, "Tab should not be be muted or playing"); + + // Make sure it's possible to mute using the context menu. + yield test_muting_using_menu(tab, false); + + // Make sure it's possible to unmute using the context menu. + yield test_muting_using_menu(tab, true); +} + +function* test_swapped_browser_while_playing(oldTab, newBrowser) { + ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab"); + is(oldTab.muteReason, null, "Expected the correct muteReason attribute on the old tab"); + ok(oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab"); + + let newTab = gBrowser.getTabForBrowser(newBrowser); + let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => { + return event.detail.changed.includes("soundplaying") && + event.detail.changed.includes("muted"); + }); + + gBrowser.swapBrowsersAndCloseOther(newTab, oldTab); + yield AttrChangePromise; + + ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab"); + is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab"); + ok(newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab"); + + let icon = document.getAnonymousElementByAttribute(newTab, "anonid", + "soundplaying-icon"); + yield test_tooltip(icon, "Unmute tab", true); +} + +function* test_swapped_browser_while_not_playing(oldTab, newBrowser) { + ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab"); + is(oldTab.muteReason, null, "Expected the correct muteReason property on the old tab"); + ok(!oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab"); + + let newTab = gBrowser.getTabForBrowser(newBrowser); + let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => { + return event.detail.changed.includes("muted"); + }); + + let AudioPlaybackPromise = new Promise(resolve => { + let observer = (subject, topic, data) => { + ok(false, "Should not see an audio-playback notification"); + }; + Services.obs.addObserver(observer, "audiochannel-activity-normal", false); + setTimeout(() => { + Services.obs.removeObserver(observer, "audiochannel-activity-normal"); + resolve(); + }, 100); + }); + + gBrowser.swapBrowsersAndCloseOther(newTab, oldTab); + yield AttrChangePromise; + + ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab"); + is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab"); + ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab"); + + // Wait to see if an audio-playback event is dispatched. + yield AudioPlaybackPromise; + + ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab"); + is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab"); + ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab"); + + let icon = document.getAnonymousElementByAttribute(newTab, "anonid", + "soundplaying-icon"); + yield test_tooltip(icon, "Unmute tab", true); +} + +function* test_browser_swapping(tab, browser) { + // First, test swapping with a playing but muted tab. + yield play(tab); + + let icon = document.getAnonymousElementByAttribute(tab, "anonid", + "soundplaying-icon"); + yield test_mute_tab(tab, icon, true); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, function*(newBrowser) { + yield test_swapped_browser_while_playing(tab, newBrowser) + + // Now, test swapping with a muted but not playing tab. + // Note that the tab remains muted, so we only need to pause playback. + tab = gBrowser.getTabForBrowser(newBrowser); + yield pause(tab); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, secondAboutBlankBrowser => test_swapped_browser_while_not_playing(tab, secondAboutBlankBrowser)); + }); +} + +function* test_click_on_pinned_tab_after_mute() { + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + gBrowser.selectedTab = originallySelectedTab; + isnot(tab, gBrowser.selectedTab, "Sanity check, the tab should not be selected!"); + + // Steps to reproduce the bug: + // Pin the tab. + gBrowser.pinTab(tab); + + // Start playback and wait for it to finish. + yield play(tab); + + // Mute the tab. + let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon"); + yield test_mute_tab(tab, icon, true); + + // Pause playback and wait for it to finish. + yield pause(tab); + + // Unmute tab. + yield test_mute_tab(tab, icon, false); + + // Now click on the tab. + let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image"); + EventUtils.synthesizeMouseAtCenter(image, {button: 0}); + + is(tab, gBrowser.selectedTab, "Tab switch should be successful"); + + // Cleanup. + gBrowser.unpinTab(tab); + gBrowser.selectedTab = originallySelectedTab; + } + + let originallySelectedTab = gBrowser.selectedTab; + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +// This test only does something useful in e10s! +function* test_cross_process_load() { + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + // Start playback and wait for it to finish. + yield play(tab); + + let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, + event => event.detail.changed.includes("soundplaying") + ); + + // Go to a different process. + browser.loadURI("about:"); + yield BrowserTestUtils.browserLoaded(browser); + + yield soundPlayingStoppedPromise; + + ok(!tab.hasAttribute("soundplaying"), "Tab should not be playing sound any more"); + ok(!tab.soundPlaying, "Tab should not be playing sound any more"); + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +function* test_mute_keybinding() { + function* test_muting_using_keyboard(tab) { + let mutedPromise = get_wait_for_mute_promise(tab, true); + EventUtils.synthesizeKey("m", {ctrlKey: true}); + yield mutedPromise; + mutedPromise = get_wait_for_mute_promise(tab, false); + EventUtils.synthesizeKey("m", {ctrlKey: true}); + yield mutedPromise; + } + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + // Make sure it's possible to mute before the tab is playing. + yield test_muting_using_keyboard(tab); + + // Start playback and wait for it to finish. + yield play(tab); + + // Make sure it's possible to mute after the tab is playing. + yield test_muting_using_keyboard(tab); + + // Pause playback and wait for it to finish. + yield pause(tab); + + // Make sure things work if the tab is pinned. + gBrowser.pinTab(tab); + + // Make sure it's possible to mute before the tab is playing. + yield test_muting_using_keyboard(tab); + + // Start playback and wait for it to finish. + yield play(tab); + + // Make sure it's possible to mute after the tab is playing. + yield test_muting_using_keyboard(tab); + + gBrowser.unpinTab(tab); + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +function* test_on_browser(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + // Test the icon in a normal tab. + yield test_playing_icon_on_tab(tab, browser, false); + + gBrowser.pinTab(tab); + + // Test the icon in a pinned tab. + yield test_playing_icon_on_tab(tab, browser, true); + + gBrowser.unpinTab(tab); + + // Retest with another browser in the foreground tab + if (gBrowser.selectedBrowser.currentURI.spec == PAGE) { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "data:text/html,test" + }, () => test_on_browser(browser)); + } else { + yield test_browser_swapping(tab, browser); + } +} + +function* test_delayed_tabattr_removal() { + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + yield play(tab); + + // Extend the delay to guarantee the soundplaying attribute + // is not removed from the tab when audio is stopped. Without + // the extended delay the attribute could be removed in the + // same tick and the test wouldn't catch that this broke. + yield pause(tab, {extendedDelay: true}); + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +add_task(function*() { + yield new Promise((resolve) => { + SpecialPowers.pushPrefEnv({"set": [ + ["browser.tabs.showAudioPlayingIcon", true], + ]}, resolve); + }); +}); + +requestLongerTimeout(2); +add_task(function* test_page() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, test_on_browser); +}); + +add_task(test_click_on_pinned_tab_after_mute); + +add_task(test_cross_process_load); + +add_task(test_mute_keybinding); + +add_task(test_delayed_tabattr_removal); diff --git a/browser/base/content/test/general/browser_backButtonFitts.js b/browser/base/content/test/general/browser_backButtonFitts.js new file mode 100644 index 000000000..0e8aeeaee --- /dev/null +++ b/browser/base/content/test/general/browser_backButtonFitts.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(function* () { + let firstLocation = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, firstLocation); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + // Push the state before maximizing the window and clicking below. + content.history.pushState("page2", "page2", "page2"); + + // While in the child process, add a listener for the popstate event here. This + // event will fire when the mouse click happens. + content.addEventListener("popstate", function onPopState() { + content.removeEventListener("popstate", onPopState, false); + sendAsyncMessage("Test:PopStateOccurred", { location: content.document.location.href }); + }, false); + }); + + window.maximize(); + + // Find where the nav-bar is vertically. + var navBar = document.getElementById("nav-bar"); + var boundingRect = navBar.getBoundingClientRect(); + var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2); + var xPixel = 0; // Use the first pixel of the screen since it is maximized. + + let resultLocation = yield new Promise(resolve => { + messageManager.addMessageListener("Test:PopStateOccurred", function statePopped(message) { + messageManager.removeMessageListener("Test:PopStateOccurred", statePopped); + resolve(message.data.location); + }); + + EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window); + }); + + is(resultLocation, firstLocation, "Clicking the first pixel should have navigated back."); + window.restore(); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js new file mode 100644 index 000000000..91a4a7e9c --- /dev/null +++ b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js @@ -0,0 +1,76 @@ +const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html"; + +var expectingDialog = false; +var wantToClose = true; +var resolveDialogPromise; +function onTabModalDialogLoaded(node) { + ok(expectingDialog, "Should be expecting this dialog."); + expectingDialog = false; + if (wantToClose) { + // This accepts the dialog, closing it + node.Dialog.ui.button0.click(); + } else { + // This keeps the page open + node.Dialog.ui.button1.click(); + } + if (resolveDialogPromise) { + resolveDialogPromise(); + } +} + +SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]}); + +// Listen for the dialog being created +Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.tabs.warnOnClose"); + Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded"); +}); + +add_task(function* closeLastTabInWindow() { + let newWin = yield promiseOpenAndLoadWindow({}, true); + let firstTab = newWin.gBrowser.selectedTab; + yield promiseTabLoadEvent(firstTab, TEST_PAGE); + let windowClosedPromise = promiseWindowWillBeClosed(newWin); + expectingDialog = true; + // close tab: + document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click(); + yield windowClosedPromise; + ok(!expectingDialog, "There should have been a dialog."); + ok(newWin.closed, "Window should be closed."); +}); + +add_task(function* closeWindowWithMultipleTabsIncludingOneBeforeUnload() { + Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); + let newWin = yield promiseOpenAndLoadWindow({}, true); + let firstTab = newWin.gBrowser.selectedTab; + yield promiseTabLoadEvent(firstTab, TEST_PAGE); + yield promiseTabLoadEvent(newWin.gBrowser.addTab(), "http://example.com/"); + let windowClosedPromise = promiseWindowWillBeClosed(newWin); + expectingDialog = true; + newWin.BrowserTryToCloseWindow(); + yield windowClosedPromise; + ok(!expectingDialog, "There should have been a dialog."); + ok(newWin.closed, "Window should be closed."); + Services.prefs.clearUserPref("browser.tabs.warnOnClose"); +}); + +add_task(function* closeWindoWithSingleTabTwice() { + let newWin = yield promiseOpenAndLoadWindow({}, true); + let firstTab = newWin.gBrowser.selectedTab; + yield promiseTabLoadEvent(firstTab, TEST_PAGE); + let windowClosedPromise = promiseWindowWillBeClosed(newWin); + expectingDialog = true; + wantToClose = false; + let firstDialogShownPromise = new Promise((resolve, reject) => { resolveDialogPromise = resolve; }); + document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click(); + yield firstDialogShownPromise; + info("Got initial dialog, now trying again"); + expectingDialog = true; + wantToClose = true; + resolveDialogPromise = null; + document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click(); + yield windowClosedPromise; + ok(!expectingDialog, "There should have been a dialog."); + ok(newWin.closed, "Window should be closed."); +}); diff --git a/browser/base/content/test/general/browser_blob-channelname.js b/browser/base/content/test/general/browser_blob-channelname.js new file mode 100644 index 000000000..d87e4a896 --- /dev/null +++ b/browser/base/content/test/general/browser_blob-channelname.js @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +Cu.import("resource://gre/modules/NetUtil.jsm"); + +function test() { + var file = new File([new Blob(['test'], {type: 'text/plain'})], "test-name"); + var url = URL.createObjectURL(file); + var channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true}); + + is(channel.contentDispositionFilename, 'test-name', "filename matches"); +} diff --git a/browser/base/content/test/general/browser_blockHPKP.js b/browser/base/content/test/general/browser_blockHPKP.js new file mode 100644 index 000000000..c0d1233ab --- /dev/null +++ b/browser/base/content/test/general/browser_blockHPKP.js @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test that visiting a site pinned with HPKP headers does not succeed when it +// uses a certificate with a key not in the pinset. This should result in an +// about:neterror page +// Also verify that removal of the HPKP headers succeeds (via HPKP headers) +// and that after removal the visit to the site with the previously +// unauthorized pins succeeds. +// +// This test required three certs to be created in build/pgo/certs: +// 1. A new trusted root: +// a. certutil -S -s "Alternate trusted authority" -s "CN=Alternate Trusted Authority" -t "C,," -x -m 1 -v 120 -n "alternateTrustedAuthority" -Z SHA256 -g 2048 -2 -d . +// b. (export) certutil -L -d . -n "alternateTrustedAuthority" -a -o alternateroot.ca +// (files ended in .ca are added as trusted roots by the mochitest harness) +// 2. A good pinning server cert (signed by the pgo root): +// certutil -S -n "dynamicPinningGood" -s "CN=dynamic-pinning.example.com" -c "pgo temporary ca" -t "P,," -k rsa -g 2048 -Z SHA256 -m 8939454 -v 120 -8 "*.include-subdomains.pinning-dynamic.example.com,*.pinning-dynamic.example.com" -d . +// 3. A certificate with a different issuer, so as to cause a key pinning violation." +// certutil -S -n "dynamicPinningBad" -s "CN=bad.include-subdomains.pinning-dynamic.example.com" -c "alternateTrustedAuthority" -t "P,," -k rsa -g 2048 -Z SHA256 -m 893945439 -v 120 -8 "bad.include-subdomains.pinning-dynamic.example.com" -d . + +const gSSService = Cc["@mozilla.org/ssservice;1"] + .getService(Ci.nsISiteSecurityService); +const gIOService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + +const kPinningDomain = "include-subdomains.pinning-dynamic.example.com"; +const khpkpPinninEnablePref = "security.cert_pinning.process_headers_from_non_builtin_roots"; +const kpkpEnforcementPref = "security.cert_pinning.enforcement_level"; +const kBadPinningDomain = "bad.include-subdomains.pinning-dynamic.example.com"; +const kURLPath = "/browser/browser/base/content/test/general/pinning_headers.sjs?"; + +function test() { + waitForExplicitFinish(); + // Enable enforcing strict pinning and processing headers from + // non-builtin roots. + Services.prefs.setIntPref(kpkpEnforcementPref, 2); + Services.prefs.setBoolPref(khpkpPinninEnablePref, true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(kpkpEnforcementPref); + Services.prefs.clearUserPref(khpkpPinninEnablePref); + let uri = gIOService.newURI("https://" + kPinningDomain, null, null); + gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0); + }); + whenNewTabLoaded(window, loadPinningPage); +} + +// Start by making a successful connection to a domain that will pin a site +function loadPinningPage() { + + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "valid").then(function() { + gBrowser.selectedBrowser.addEventListener("load", + successfulPinningPageListener, + true); + }); +} + +// After the site is pinned try to load with a subdomain site that should +// fail to validate +var successfulPinningPageListener = { + handleEvent: function() { + gBrowser.selectedBrowser.removeEventListener("load", this, true); + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() { + return promiseErrorPageLoaded(gBrowser.selectedBrowser); + }).then(errorPageLoaded); + } +}; + +// The browser should load about:neterror, when this happens, proceed +// to load the pinning domain again, this time removing the pinning information +function errorPageLoaded() { + ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let textElement = content.document.getElementById("errorShortDescText"); + let text = textElement.innerHTML; + ok(text.indexOf("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE") > 0, + "Got a pinning error page"); + }).then(function() { + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "zeromaxagevalid").then(function() { + return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + }).then(pinningRemovalLoaded); + }); +} + +// After the pinning information has been removed (successful load) proceed +// to load again with the invalid pin domain. +function pinningRemovalLoaded() { + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() { + return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + }).then(badPinningPageLoaded); +} + +// Finally, we should successfully load +// https://bad.include-subdomains.pinning-dynamic.example.com. +function badPinningPageLoaded() { + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(function() { + ok(true, "load complete"); + finish(); + }); +} diff --git a/browser/base/content/test/general/browser_bookmark_popup.js b/browser/base/content/test/general/browser_bookmark_popup.js new file mode 100644 index 000000000..c1ddd725e --- /dev/null +++ b/browser/base/content/test/general/browser_bookmark_popup.js @@ -0,0 +1,431 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Test opening and closing the bookmarks panel. + */ + +let bookmarkPanel = document.getElementById("editBookmarkPanel"); +let bookmarkStar = document.getElementById("bookmarks-menu-button"); +let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle"); +let editBookmarkPanelRemoveButtonRect; + +StarUI._closePanelQuickForTesting = true; + +function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn, + shouldAutoClose, popupHideFn, isBookmarkRemoved}) { + yield BrowserTestUtils.withNewTab({gBrowser, url: "about:home"}, function*(browser) { + try { + if (!isNewBookmark) { + yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "about:home", + title: "Home Page" + }); + } + + info(`BookmarkingUI.status is ${BookmarkingUI.status}`); + yield BrowserTestUtils.waitForCondition( + () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING, + "BookmarkingUI should not be updating"); + + is(bookmarkStar.hasAttribute("starred"), !isNewBookmark, + "Page should only be starred prior to popupshown if editing bookmark"); + is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test"); + let shownPromise = promisePopupShown(bookmarkPanel); + yield popupShowFn(browser); + yield shownPromise; + is(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved"); + + editBookmarkPanelRemoveButtonRect = + document.getElementById("editBookmarkPanelRemoveButton").getBoundingClientRect(); + + if (popupEditFn) { + yield popupEditFn(); + } + let bookmarks = []; + yield PlacesUtils.bookmarks.fetch({url: "about:home"}, bm => bookmarks.push(bm)); + is(bookmarks.length, 1, "Only one bookmark should exist"); + is(bookmarkStar.getAttribute("starred"), "true", "Page is starred"); + is(bookmarkPanelTitle.value, + isNewBookmark ? + gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") : + gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"), + "title should match isEditingBookmark state"); + + if (!shouldAutoClose) { + yield new Promise(resolve => setTimeout(resolve, 400)); + is(bookmarkPanel.state, "open", "Panel should still be 'open' for non-autoclose"); + } + + let hiddenPromise = promisePopupHidden(bookmarkPanel); + if (popupHideFn) { + yield popupHideFn(); + } + yield hiddenPromise; + is(bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved, + "Page is starred after closing"); + } finally { + let bookmark = yield PlacesUtils.bookmarks.fetch({url: "about:home"}); + is(!!bookmark, !isBookmarkRemoved, + "bookmark should not be present if a panel action should've removed it"); + if (bookmark) { + yield PlacesUtils.bookmarks.remove(bookmark); + } + } + }); +} + +add_task(function* panel_shown_for_new_bookmarks_and_autocloses() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + bookmarkStar.click(); + }, + shouldAutoClose: true, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_once_for_doubleclick_on_new_bookmark_star_and_autocloses() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + EventUtils.synthesizeMouse(bookmarkStar, 10, 10, { clickCount: 2 }, + window); + }, + shouldAutoClose: true, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() { + todo(false, "bug 1250267, may need to add some tracking state to " + + "browser-places.js for this."); + return; + + /* + yield test_bookmarks_popup({ + isNewBookmark: true, + *popupShowFn() { + EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window); + yield new Promise(resolve => setTimeout(resolve, 300)); + EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window); + }, + shouldAutoClose: true, + isBookmarkRemoved: false, + }); + */ +}); + +add_task(function* panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + EventUtils.synthesizeKey("D", {accelKey: true}, window); + }, + shouldAutoClose: true, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_for_new_bookmarks_mousemove_mouseout() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + bookmarkStar.click(); + }, + *popupEditFn() { + let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove"); + EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"}); + info("Waiting for mousemove event"); + yield mouseMovePromise; + info("Got mousemove event"); + + yield new Promise(resolve => setTimeout(resolve, 400)); + is(bookmarkPanel.state, "open", "Panel should still be open on mousemove"); + }, + *popupHideFn() { + let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout"); + EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"}); + EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); + info("Waiting for mouseout event"); + yield mouseOutPromise; + info("Got mouseout event, should autoclose now"); + }, + shouldAutoClose: false, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_for_new_bookmark_no_autoclose_close_with_ESC() { + yield test_bookmarks_popup({ + isNewBookmark: false, + popupShowFn() { + bookmarkStar.click(); + }, + shouldAutoClose: false, + popupHideFn() { + EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window); + }, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_for_editing_no_autoclose_close_with_ESC() { + yield test_bookmarks_popup({ + isNewBookmark: false, + popupShowFn() { + bookmarkStar.click(); + }, + shouldAutoClose: false, + popupHideFn() { + EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window); + }, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_for_new_bookmark_keypress_no_autoclose() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + bookmarkStar.click(); + }, + popupEditFn() { + EventUtils.sendChar("VK_TAB", window); + }, + shouldAutoClose: false, + popupHideFn() { + bookmarkPanel.hidePopup(); + }, + isBookmarkRemoved: false, + }); +}); + + +add_task(function* panel_shown_for_new_bookmark_compositionstart_no_autoclose() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + bookmarkStar.click(); + }, + *popupEditFn() { + let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart"); + EventUtils.synthesizeComposition({ type: "compositionstart" }, window); + info("Waiting for compositionstart event"); + yield compositionStartPromise; + info("Got compositionstart event"); + }, + shouldAutoClose: false, + popupHideFn() { + EventUtils.synthesizeComposition({ type: "compositioncommitasis" }); + bookmarkPanel.hidePopup(); + }, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_for_new_bookmark_compositionstart_mouseout_no_autoclose() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + bookmarkStar.click(); + }, + *popupEditFn() { + let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove"); + EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"}); + info("Waiting for mousemove event"); + yield mouseMovePromise; + info("Got mousemove event"); + + let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart"); + EventUtils.synthesizeComposition({ type: "compositionstart" }, window); + info("Waiting for compositionstart event"); + yield compositionStartPromise; + info("Got compositionstart event"); + + let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout"); + EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"}); + EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); + info("Waiting for mouseout event"); + yield mouseOutPromise; + info("Got mouseout event, but shouldn't run autoclose"); + }, + shouldAutoClose: false, + popupHideFn() { + EventUtils.synthesizeComposition({ type: "compositioncommitasis" }); + bookmarkPanel.hidePopup(); + }, + isBookmarkRemoved: false, + }); +}); + +add_task(function* panel_shown_for_new_bookmark_compositionend_no_autoclose() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn() { + bookmarkStar.click(); + }, + *popupEditFn() { + let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove"); + EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"}); + info("Waiting for mousemove event"); + yield mouseMovePromise; + info("Got mousemove event"); + + EventUtils.synthesizeComposition({ type: "compositioncommit", data: "committed text" }); + }, + popupHideFn() { + bookmarkPanel.hidePopup(); + }, + shouldAutoClose: false, + isBookmarkRemoved: false, + }); +}); + +add_task(function* contextmenu_new_bookmark_keypress_no_autoclose() { + yield test_bookmarks_popup({ + isNewBookmark: true, + *popupShowFn(browser) { + let contextMenu = document.getElementById("contentAreaContextMenu"); + let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, + "popupshown"); + let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, + "popuphidden"); + yield BrowserTestUtils.synthesizeMouseAtCenter("body", { + type: "contextmenu", + button: 2 + }, browser); + yield awaitPopupShown; + document.getElementById("context-bookmarkpage").click(); + contextMenu.hidePopup(); + yield awaitPopupHidden; + }, + popupEditFn() { + EventUtils.sendChar("VK_TAB", window); + }, + shouldAutoClose: false, + popupHideFn() { + bookmarkPanel.hidePopup(); + }, + isBookmarkRemoved: false, + }); +}); + +add_task(function* bookmarks_menu_new_bookmark_remove_bookmark() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn(browser) { + document.getElementById("menu_bookmarkThisPage").doCommand(); + }, + shouldAutoClose: true, + popupHideFn() { + document.getElementById("editBookmarkPanelRemoveButton").click(); + }, + isBookmarkRemoved: true, + }); +}); + +add_task(function* ctrl_d_edit_bookmark_remove_bookmark() { + yield test_bookmarks_popup({ + isNewBookmark: false, + popupShowFn(browser) { + EventUtils.synthesizeKey("D", {accelKey: true}, window); + }, + shouldAutoClose: true, + popupHideFn() { + document.getElementById("editBookmarkPanelRemoveButton").click(); + }, + isBookmarkRemoved: true, + }); +}); + +add_task(function* enter_on_remove_bookmark_should_remove_bookmark() { + if (AppConstants.platform == "macosx") { + // "Full Keyboard Access" is disabled by default, and thus doesn't allow + // keyboard navigation to the "Remove Bookmarks" button by default. + return; + } + + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn(browser) { + EventUtils.synthesizeKey("D", {accelKey: true}, window); + }, + shouldAutoClose: true, + popupHideFn() { + while (!document.activeElement || + document.activeElement.id != "editBookmarkPanelRemoveButton") { + EventUtils.sendChar("VK_TAB", window); + } + EventUtils.sendChar("VK_RETURN", window); + }, + isBookmarkRemoved: true, + }); +}); + +add_task(function* ctrl_d_new_bookmark_mousedown_mouseout_no_autoclose() { + yield test_bookmarks_popup({ + isNewBookmark: true, + popupShowFn(browser) { + EventUtils.synthesizeKey("D", {accelKey: true}, window); + }, + *popupEditFn() { + let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove"); + EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"}); + info("Waiting for mousemove event"); + yield mouseMovePromise; + info("Got mousemove event"); + + yield new Promise(resolve => setTimeout(resolve, 400)); + is(bookmarkPanel.state, "open", "Panel should still be open on mousemove"); + + EventUtils.synthesizeMouseAtCenter(bookmarkPanelTitle, {button: 1, type: "mousedown"}); + + let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout"); + EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"}); + EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); + info("Waiting for mouseout event"); + yield mouseOutPromise; + }, + shouldAutoClose: false, + popupHideFn() { + document.getElementById("editBookmarkPanelRemoveButton").click(); + }, + isBookmarkRemoved: true, + }); +}); + +add_task(function* mouse_hovering_panel_should_prevent_autoclose() { + if (AppConstants.platform != "win") { + // This test requires synthesizing native mouse movement which is + // best supported on Windows. + return; + } + yield test_bookmarks_popup({ + isNewBookmark: true, + *popupShowFn(browser) { + yield new Promise(resolve => { + EventUtils.synthesizeNativeMouseMove( + document.documentElement, + editBookmarkPanelRemoveButtonRect.left, + editBookmarkPanelRemoveButtonRect.top, + resolve); + }); + EventUtils.synthesizeKey("D", {accelKey: true}, window); + }, + shouldAutoClose: false, + popupHideFn() { + document.getElementById("editBookmarkPanelRemoveButton").click(); + }, + isBookmarkRemoved: true, + }); +}); + +registerCleanupFunction(function() { + delete StarUI._closePanelQuickForTesting; +}); diff --git a/browser/base/content/test/general/browser_bookmark_titles.js b/browser/base/content/test/general/browser_bookmark_titles.js new file mode 100644 index 000000000..1f7082396 --- /dev/null +++ b/browser/base/content/test/general/browser_bookmark_titles.js @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file is tests for the default titles that new bookmarks get. + +var tests = [ + // Common page. + ['http://example.com/browser/browser/base/content/test/general/dummy_page.html', + 'Dummy test page'], + // Data URI. + ['data:text/html;charset=utf-8,<title>test%20data:%20url</title>', + 'test data: url'], + // about:neterror + ['data:application/vnd.mozilla.xul+xml,', + 'data:application/vnd.mozilla.xul+xml,'], + // about:certerror + ['https://untrusted.example.com/somepage.html', + 'https://untrusted.example.com/somepage.html'] +]; + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + browser.stop(); // stop the about:blank load. + + // Test that a bookmark of each URI gets the corresponding default title. + for (let i = 0; i < tests.length; ++i) { + let [uri, title] = tests[i]; + + let promiseLoaded = promisePageLoaded(browser); + BrowserTestUtils.loadURI(browser, uri); + yield promiseLoaded; + yield checkBookmark(uri, title); + } + + // Network failure test: now that dummy_page.html is in history, bookmarking + // it should give the last known page title as the default bookmark title. + + // Simulate a network outage with offline mode. (Localhost is still + // accessible in offline mode, so disable the test proxy as well.) + BrowserOffline.toggleOfflineStatus(); + let proxy = Services.prefs.getIntPref('network.proxy.type'); + Services.prefs.setIntPref('network.proxy.type', 0); + registerCleanupFunction(function () { + BrowserOffline.toggleOfflineStatus(); + Services.prefs.setIntPref('network.proxy.type', proxy); + }); + + // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache. + Services.cache2.clear(); + + let [uri, title] = tests[0]; + + let promiseLoaded = promisePageLoaded(browser); + BrowserTestUtils.loadURI(browser, uri); + yield promiseLoaded; + + // The offline mode test is only good if the page failed to load. + yield ContentTask.spawn(browser, null, function() { + is(content.document.documentURI.substring(0, 14), 'about:neterror', + "Offline mode successfully simulated network outage."); + }); + yield checkBookmark(uri, title); + + gBrowser.removeCurrentTab(); +}); + +// Bookmark the current page and confirm that the new bookmark has the expected +// title. (Then delete the bookmark.) +function* checkBookmark(uri, expected_title) { + is(gBrowser.selectedBrowser.currentURI.spec, uri, + "Trying to bookmark the expected uri"); + + let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.selectedBrowser.currentURI); + PlacesCommandHook.bookmarkCurrentPage(false); + yield promiseBookmark; + + let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri)); + ok(id > 0, "Found the expected bookmark"); + let title = PlacesUtils.bookmarks.getItemTitle(id); + is(title, expected_title, "Bookmark got a good default title."); + + PlacesUtils.bookmarks.removeItem(id); +} + +// BrowserTestUtils.browserLoaded doesn't work for the about pages, so use a +// custom page load listener. +function promisePageLoaded(browser) +{ + return ContentTask.spawn(browser, null, function* () { + yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true, + (event) => { + return event.originalTarget === content.document && + event.target.location.href !== "about:blank" + }); + }); +} diff --git a/browser/base/content/test/general/browser_bug1015721.js b/browser/base/content/test/general/browser_bug1015721.js new file mode 100644 index 000000000..e3e715396 --- /dev/null +++ b/browser/base/content/test/general/browser_bug1015721.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html"; + +var gTab1, gTab2, gLevel1; + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + gTab1 = gBrowser.addTab(); + gTab2 = gBrowser.addTab(); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + yield FullZoomHelper.load(gTab1, TEST_PAGE); + yield FullZoomHelper.load(gTab2, TEST_PAGE); + }).then(zoomTab1, FullZoomHelper.failAndContinue(finish)); +} + +function zoomTab1() { + Task.spawn(function* () { + is(gBrowser.selectedTab, gTab1, "Tab 1 is selected"); + FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1"); + FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1"); + + let browser1 = gBrowser.getBrowserForTab(gTab1); + yield BrowserTestUtils.synthesizeMouse(null, 10, 10, { + wheel: true, ctrlKey: true, deltaY: -1, deltaMode: WheelEvent.DOM_DELTA_LINE + }, browser1); + + info("Waiting for tab 1 to be zoomed"); + yield promiseWaitForCondition(() => { + gLevel1 = ZoomManager.getZoomForBrowser(browser1); + return gLevel1 > 1; + }); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2); + FullZoomHelper.zoomTest(gTab2, gLevel1, "Tab 2 should have zoomed along with tab 1"); + }).then(finishTest, FullZoomHelper.failAndContinue(finish)); +} + +function finishTest() { + Task.spawn(function* () { + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1); + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2); + }).then(finish, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug1045809.js b/browser/base/content/test/general/browser_bug1045809.js new file mode 100644 index 000000000..63b6b06d5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug1045809.js @@ -0,0 +1,68 @@ +// Test that the Mixed Content Doorhanger Action to re-enable protection works + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; + +var origBlockActive; + +add_task(function* () { + registerCleanupFunction(function() { + Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive); + gBrowser.removeCurrentTab(); + }); + + // Store original preferences so we can restore settings after testing + origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE); + + // Make sure mixed content blocking is on + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + var url = + "https://test1.example.com/browser/browser/base/content/test/general/" + + "file_bug1045809_1.html"; + let tab = gBrowser.selectedTab = gBrowser.addTab(); + + // Test 1: mixed content must be blocked + yield promiseTabLoadEvent(tab, url); + yield* test1(gBrowser.getBrowserForTab(tab)); + + yield promiseTabLoadEvent(tab); + // Test 2: mixed content must NOT be blocked + yield* test2(gBrowser.getBrowserForTab(tab)); + + // Test 3: mixed content must be blocked again + yield promiseTabLoadEvent(tab); + yield* test3(gBrowser.getBrowserForTab(tab)); +}); + +function* test1(gTestBrowser) { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + yield ContentTask.spawn(gTestBrowser, null, function() { + var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer"); + is(x, null, "Mixed Content is NOT to be found in Test1"); + }); + + // Disable Mixed Content Protection for the page (and reload) + gIdentityHandler.disableMixedContentProtection(); +} + +function* test2(gTestBrowser) { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false}); + + yield ContentTask.spawn(gTestBrowser, null, function() { + var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer"); + isnot(x, null, "Mixed Content is to be found in Test2"); + }); + + // Re-enable Mixed Content Protection for the page (and reload) + gIdentityHandler.enableMixedContentProtection(); +} + +function* test3(gTestBrowser) { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + yield ContentTask.spawn(gTestBrowser, null, function() { + var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer"); + is(x, null, "Mixed Content is NOT to be found in Test3"); + }); +} diff --git a/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js new file mode 100644 index 000000000..98e0e74db --- /dev/null +++ b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* () { + // Test that changing the URL in a pinned tab works correctly + + let TEST_LINK_INITIAL = "about:"; + let TEST_LINK_CHANGED = "about:support"; + + let appTab = gBrowser.addTab(TEST_LINK_INITIAL); + let browser = appTab.linkedBrowser; + yield BrowserTestUtils.browserLoaded(browser); + + gBrowser.pinTab(appTab); + is(appTab.pinned, true, "Tab was successfully pinned"); + + let initialTabsNo = gBrowser.tabs.length; + + let goButton = document.getElementById("urlbar-go-button"); + gBrowser.selectedTab = appTab; + gURLBar.focus(); + gURLBar.value = TEST_LINK_CHANGED; + + goButton.click(); + yield BrowserTestUtils.browserLoaded(browser); + + is(appTab.linkedBrowser.currentURI.spec, TEST_LINK_CHANGED, + "New page loaded in the app tab"); + is(gBrowser.tabs.length, initialTabsNo, "No additional tabs were opened"); +}); + +registerCleanupFunction(function () { + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/general/browser_bug1261299.js b/browser/base/content/test/general/browser_bug1261299.js new file mode 100644 index 000000000..673ef2a0a --- /dev/null +++ b/browser/base/content/test/general/browser_bug1261299.js @@ -0,0 +1,73 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests for Bug 1261299 + * Test that the service menu code path is called properly and the + * current selection (transferable) is cached properly on the parent process. + */ + +add_task(function* test_content_and_chrome_selection() +{ + let testPage = + 'data:text/html,' + + '<textarea id="textarea">Write something here</textarea>'; + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + let selectedText; + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage); + yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight", + {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is(selectedText, "Write something here", "The macOS services got the selected content text"); + + gURLBar.value = "test.mozilla.org"; + yield gURLBar.focus(); + yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight", + {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is(selectedText, "test.mozilla.org", "The macOS services got the selected chrome text"); + + yield BrowserTestUtils.removeTab(tab); +}); + +// Test switching active selection. +// Each tab has a content selection and when you switch to that tab, its selection becomes +// active aka the current selection. +// Expect: The active selection is what is being sent to OSX service menu. + +add_task(function* test_active_selection_switches_properly() +{ + let testPage1 = + 'data:text/html,' + + '<textarea id="textarea">Write something here</textarea>'; + let testPage2 = + 'data:text/html,' + + '<textarea id="textarea">Nothing available</textarea>'; + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + let selectedText; + + let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1); + yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight", + {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser); + + let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2); + yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight", + {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser); + + yield BrowserTestUtils.switchTab(gBrowser, tab1); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is(selectedText, "Write something here", "The macOS services got the selected content text"); + + yield BrowserTestUtils.switchTab(gBrowser, tab2); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is(selectedText, "Nothing available", "The macOS services got the selected content text"); + + yield BrowserTestUtils.removeTab(tab1); + yield BrowserTestUtils.removeTab(tab2); +}); diff --git a/browser/base/content/test/general/browser_bug1297539.js b/browser/base/content/test/general/browser_bug1297539.js new file mode 100644 index 000000000..d7e675437 --- /dev/null +++ b/browser/base/content/test/general/browser_bug1297539.js @@ -0,0 +1,114 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test for Bug 1297539 + * Test that the content event "pasteTransferable" + * (mozilla::EventMessage::eContentCommandPasteTransferable) + * is handled correctly for plain text and html in the remote case. + * + * Original test test_bug525389.html for command content event + * "pasteTransferable" runs only in the content process. + * This doesn't test the remote case. + * + */ + +"use strict"; + +function getLoadContext() { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext); +} + +function getTransferableFromClipboard(asHTML) { + let trans = Cc["@mozilla.org/widget/transferable;1"]. + createInstance(Ci.nsITransferable); + trans.init(getLoadContext()); + if (asHTML) { + trans.addDataFlavor("text/html"); + } else { + trans.addDataFlavor("text/unicode"); + } + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard); + clip.getData(trans, Ci.nsIClipboard.kGlobalClipboard); + return trans; +} + +function* cutCurrentSelection(elementQueryString, property, browser) { + // Cut the current selection. + yield BrowserTestUtils.synthesizeKey("x", {accelKey: true}, browser); + + // The editor should be empty after cut. + yield ContentTask.spawn(browser, [elementQueryString, property], + function* ([contentElementQueryString, contentProperty]) { + let element = content.document.querySelector(contentElementQueryString); + is(element[contentProperty], "", + `${contentElementQueryString} should be empty after cut (superkey + x)`); + }); +} + +// Test that you are able to pasteTransferable for plain text +// which is handled by TextEditor::PasteTransferable to paste into the editor. +add_task(function* test_paste_transferable_plain_text() +{ + let testPage = + 'data:text/html,' + + '<textarea id="textarea">Write something here</textarea>'; + + yield BrowserTestUtils.withNewTab(testPage, function* (browser) { + // Select all the content in your editor element. + yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, browser); + yield BrowserTestUtils.synthesizeKey("a", {accelKey: true}, browser); + + yield* cutCurrentSelection("#textarea", "value", browser); + + let trans = getTransferableFromClipboard(false); + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans); + + yield ContentTask.spawn(browser, null, function* () { + let textArea = content.document.querySelector('#textarea'); + is(textArea.value, "Write something here", + "Send content command pasteTransferable successful"); + }); + }); +}); + +// Test that you are able to pasteTransferable for html +// which is handled by HTMLEditor::PasteTransferable to paste into the editor. +// +// On Linux, +// BrowserTestUtils.synthesizeKey("a", {accelKey: true}, browser); +// doesn't seem to trigger for contenteditable which is why we use +// Selection to select the contenteditable contents. +add_task(function* test_paste_transferable_html() +{ + let testPage = + 'data:text/html,' + + '<div contenteditable="true"><b>Bold Text</b><i>italics</i></div>'; + + yield BrowserTestUtils.withNewTab(testPage, function* (browser) { + // Select all the content in your editor element. + yield BrowserTestUtils.synthesizeMouse("div", 0, 0, {}, browser); + yield ContentTask.spawn(browser, {}, function* () { + let element = content.document.querySelector("div"); + let selection = content.window.getSelection(); + selection.selectAllChildren(element); + }); + + yield* cutCurrentSelection("div", "textContent", browser); + + let trans = getTransferableFromClipboard(true); + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans); + + yield ContentTask.spawn(browser, null, function* () { + let textArea = content.document.querySelector('div'); + is(textArea.innerHTML, "<b>Bold Text</b><i>italics</i>", + "Send content command pasteTransferable successful"); + }); + }); +}); diff --git a/browser/base/content/test/general/browser_bug1299667.js b/browser/base/content/test/general/browser_bug1299667.js new file mode 100644 index 000000000..084c8d49f --- /dev/null +++ b/browser/base/content/test/general/browser_bug1299667.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { addObserver, removeObserver } = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + +function receive(topic) { + return new Promise((resolve, reject) => { + let timeout = setTimeout(() => { + reject(new Error("Timeout")); + }, 90000); + + const observer = { + observe: subject => { + removeObserver(observer, topic); + clearTimeout(timeout); + resolve(subject); + } + }; + addObserver(observer, topic, false); + }); +} + +add_task(function* () { + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + content.history.pushState({}, "2", "2.html"); + }); + + yield receive("sessionstore-state-write-complete"); + + // Wait for the session data to be flushed before continuing the test + yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)); + + let backButton = document.getElementById("back-button"); + let contextMenu = document.getElementById("backForwardMenu"); + + info("waiting for the history menu to open"); + + let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(backButton, {type: "contextmenu", button: 2}); + let event = yield popupShownPromise; + + ok(true, "history menu opened"); + + // Wait for the session data to be flushed before continuing the test + yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)); + + is(event.target.children.length, 2, "Two history items"); + + let node = event.target.firstChild; + is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri"); + is(node.getAttribute("index"), "1", "first item index"); + is(node.getAttribute("historyindex"), "0", "first item historyindex"); + + node = event.target.lastChild; + is(node.getAttribute("uri"), "http://example.com/", "second item uri"); + is(node.getAttribute("index"), "0", "second item index"); + is(node.getAttribute("historyindex"), "-1", "second item historyindex"); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + event.target.hidePopup(); + yield popupHiddenPromise; + info("Hidden popup"); + + let onClose = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose"); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + yield onClose; + info("Tab closed"); +}); diff --git a/browser/base/content/test/general/browser_bug321000.js b/browser/base/content/test/general/browser_bug321000.js new file mode 100644 index 000000000..b30b7101d --- /dev/null +++ b/browser/base/content/test/general/browser_bug321000.js @@ -0,0 +1,80 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const kTestString = " hello hello \n world\nworld "; + +var gTests = [ + + { desc: "Urlbar strips newlines and surrounding whitespace", + element: gURLBar, + expected: kTestString.replace(/\s*\n\s*/g, '') + }, + + { desc: "Searchbar replaces newlines with spaces", + element: document.getElementById('searchbar'), + expected: kTestString.replace(/\n/g, ' ') + }, + +]; + +// Test for bug 23485 and bug 321000. +// Urlbar should strip newlines, +// search bar should replace newlines with spaces. +function test() { + waitForExplicitFinish(); + + let cbHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]. + getService(Ci.nsIClipboardHelper); + + // Put a multi-line string in the clipboard. + // Setting the clipboard value is an async OS operation, so we need to poll + // the clipboard for valid data before going on. + waitForClipboard(kTestString, function() { cbHelper.copyString(kTestString); }, + next_test, finish); +} + +function next_test() { + if (gTests.length) + test_paste(gTests.shift()); + else + finish(); +} + +function test_paste(aCurrentTest) { + var element = aCurrentTest.element; + + // Register input listener. + var inputListener = { + test: aCurrentTest, + handleEvent: function(event) { + element.removeEventListener(event.type, this, false); + + is(element.value, this.test.expected, this.test.desc); + + // Clear the field and go to next test. + element.value = ""; + setTimeout(next_test, 0); + } + } + element.addEventListener("input", inputListener, false); + + // Focus the window. + window.focus(); + gBrowser.selectedBrowser.focus(); + + // Focus the element and wait for focus event. + info("About to focus " + element.id); + element.addEventListener("focus", function() { + element.removeEventListener("focus", arguments.callee, false); + executeSoon(function() { + // Pasting is async because the Accel+V codepath ends up going through + // nsDocumentViewer::FireClipboardEvent. + info("Pasting into " + element.id); + EventUtils.synthesizeKey("v", { accelKey: true }); + }); + }, false); + element.focus(); +} diff --git a/browser/base/content/test/general/browser_bug356571.js b/browser/base/content/test/general/browser_bug356571.js new file mode 100644 index 000000000..ab689d0f8 --- /dev/null +++ b/browser/base/content/test/general/browser_bug356571.js @@ -0,0 +1,93 @@ +// Bug 356571 - loadOneOrMoreURIs gives up if one of the URLs has an unknown protocol + +var Cr = Components.results; +var Cm = Components.manager; + +// Set to true when docShell alerts for unknown protocol error +var didFail = false; + +// Override Alert to avoid blocking the test due to unknown protocol error +const kPromptServiceUUID = "{6cc9c9fe-bc0b-432b-a410-253ef8bcc699}"; +const kPromptServiceContractID = "@mozilla.org/embedcomp/prompt-service;1"; + +// Save original prompt service factory +const kPromptServiceFactory = Cm.getClassObject(Cc[kPromptServiceContractID], + Ci.nsIFactory); + +var fakePromptServiceFactory = { + createInstance: function(aOuter, aIid) { + if (aOuter != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return promptService.QueryInterface(aIid); + } +}; + +var promptService = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]), + alert: function() { + didFail = true; + } +}; + +/* FIXME +Cm.QueryInterface(Ci.nsIComponentRegistrar) + .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service", + kPromptServiceContractID, fakePromptServiceFactory); +*/ + +const kCompleteState = Ci.nsIWebProgressListener.STATE_STOP + + Ci.nsIWebProgressListener.STATE_IS_NETWORK; + +const kDummyPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; +const kURIs = [ + "bad://www.mozilla.org/", + kDummyPage, + kDummyPage, +]; + +var gProgressListener = { + _runCount: 0, + onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + if ((aStateFlags & kCompleteState) == kCompleteState) { + if (++this._runCount != kURIs.length) + return; + // Check we failed on unknown protocol (received an alert from docShell) + ok(didFail, "Correctly failed on unknown protocol"); + // Check we opened all tabs + ok(gBrowser.tabs.length == kURIs.length, "Correctly opened all expected tabs"); + finishTest(); + } + } +} + +function test() { + todo(false, "temp. disabled"); + return; /* FIXME */ + /* + waitForExplicitFinish(); + // Wait for all tabs to finish loading + gBrowser.addTabsProgressListener(gProgressListener); + loadOneOrMoreURIs(kURIs.join("|")); + */ +} + +function finishTest() { + // Unregister the factory so we do not leak + Cm.QueryInterface(Ci.nsIComponentRegistrar) + .unregisterFactory(Components.ID(kPromptServiceUUID), + fakePromptServiceFactory); + + // Restore the original factory + Cm.QueryInterface(Ci.nsIComponentRegistrar) + .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service", + kPromptServiceContractID, kPromptServiceFactory); + + // Remove the listener + gBrowser.removeTabsProgressListener(gProgressListener); + + // Close opened tabs + for (var i = gBrowser.tabs.length-1; i > 0; i--) + gBrowser.removeTab(gBrowser.tabs[i]); + + finish(); +} diff --git a/browser/base/content/test/general/browser_bug380960.js b/browser/base/content/test/general/browser_bug380960.js new file mode 100644 index 000000000..d6b64543b --- /dev/null +++ b/browser/base/content/test/general/browser_bug380960.js @@ -0,0 +1,11 @@ +function test() { + var tab = gBrowser.addTab("about:blank", { skipAnimation: true }); + gBrowser.removeTab(tab); + is(tab.parentNode, null, "tab removed immediately"); + + tab = gBrowser.addTab("about:blank", { skipAnimation: true }); + gBrowser.removeTab(tab, { animate: true }); + gBrowser.removeTab(tab); + is(tab.parentNode, null, "tab removed immediately when calling removeTab again after the animation was kicked off"); +} + diff --git a/browser/base/content/test/general/browser_bug386835.js b/browser/base/content/test/general/browser_bug386835.js new file mode 100644 index 000000000..1c3ba99c5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug386835.js @@ -0,0 +1,89 @@ +var gTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; +var gTestImage = "http://example.org/browser/browser/base/content/test/general/moz.png"; +var gTab1, gTab2, gTab3; +var gLevel; +const BACK = 0; +const FORWARD = 1; + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + gTab1 = gBrowser.addTab(gTestPage); + gTab2 = gBrowser.addTab(); + gTab3 = gBrowser.addTab(); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + yield FullZoomHelper.load(gTab1, gTestPage); + yield FullZoomHelper.load(gTab2, gTestPage); + }).then(secondPageLoaded, FullZoomHelper.failAndContinue(finish)); +} + +function secondPageLoaded() { + Task.spawn(function* () { + FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1"); + FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1"); + FullZoomHelper.zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1"); + + // Now have three tabs, two with the test page, one blank. Tab 1 is selected + // Zoom tab 1 + FullZoom.enlarge(); + gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1)); + + ok(gLevel > 1, "New zoom for tab 1 should be greater than 1"); + FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2"); + FullZoomHelper.zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3"); + + yield FullZoomHelper.load(gTab3, gTestPage); + }).then(thirdPageLoaded, FullZoomHelper.failAndContinue(finish)); +} + +function thirdPageLoaded() { + Task.spawn(function* () { + FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed"); + FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 should still not be affected"); + FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should have zoomed as it was loading in the background"); + + // Switching to tab 2 should update its zoom setting. + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2); + FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed"); + FullZoomHelper.zoomTest(gTab2, gLevel, "Tab 2 should be zoomed now"); + FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should still be zoomed"); + + yield FullZoomHelper.load(gTab1, gTestImage); + }).then(imageLoaded, FullZoomHelper.failAndContinue(finish)); +} + +function imageLoaded() { + Task.spawn(function* () { + FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when image was loaded in the background"); + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + FullZoomHelper.zoomTest(gTab1, 1, "Zoom should still be 1 when tab with image is selected"); + }).then(imageZoomSwitch, FullZoomHelper.failAndContinue(finish)); +} + +function imageZoomSwitch() { + Task.spawn(function* () { + yield FullZoomHelper.navigate(BACK); + yield FullZoomHelper.navigate(FORWARD); + FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should not be zoomed when an image loads"); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2); + FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should still not be zoomed when deselected"); + }).then(finishTest, FullZoomHelper.failAndContinue(finish)); +} + +var finishTestStarted = false; +function finishTest() { + Task.spawn(function* () { + ok(!finishTestStarted, "finishTest called more than once"); + finishTestStarted = true; + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab3); + }).then(finish, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug406216.js b/browser/base/content/test/general/browser_bug406216.js new file mode 100644 index 000000000..e1bd38395 --- /dev/null +++ b/browser/base/content/test/general/browser_bug406216.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * "TabClose" event is possibly used for closing related tabs of the current. + * "removeTab" method should work correctly even if the number of tabs are + * changed while "TabClose" event. + */ + +var count = 0; +const URIS = ["about:config", + "about:plugins", + "about:buildconfig", + "data:text/html,<title>OK</title>"]; + +function test() { + waitForExplicitFinish(); + URIS.forEach(addTab); +} + +function addTab(aURI, aIndex) { + var tab = gBrowser.addTab(aURI); + if (aIndex == 0) + gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true}); + + tab.linkedBrowser.addEventListener("load", function (event) { + event.currentTarget.removeEventListener("load", arguments.callee, true); + if (++count == URIS.length) + executeSoon(doTabsTest); + }, true); +} + +function doTabsTest() { + is(gBrowser.tabs.length, URIS.length, "Correctly opened all expected tabs"); + + // sample of "close related tabs" feature + gBrowser.tabContainer.addEventListener("TabClose", function (event) { + event.currentTarget.removeEventListener("TabClose", arguments.callee, true); + var closedTab = event.originalTarget; + var scheme = closedTab.linkedBrowser.currentURI.scheme; + Array.slice(gBrowser.tabs).forEach(function (aTab) { + if (aTab != closedTab && aTab.linkedBrowser.currentURI.scheme == scheme) + gBrowser.removeTab(aTab, {skipPermitUnload: true}); + }); + }, true); + + gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true}); + is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly"); + + gBrowser.addTab("about:blank"); + gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true}); + finish(); +} diff --git a/browser/base/content/test/general/browser_bug408415.js b/browser/base/content/test/general/browser_bug408415.js new file mode 100644 index 000000000..d8f80f8be --- /dev/null +++ b/browser/base/content/test/general/browser_bug408415.js @@ -0,0 +1,45 @@ +add_task(function* test() { + let testPath = getRootDirectory(gTestPath); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, + function* (tabBrowser) { + const URI = testPath + "file_with_favicon.html"; + const expectedIcon = testPath + "file_generic_favicon.ico"; + + let got_favicon = Promise.defer(); + let listener = { + onLinkIconAvailable(browser, iconURI) { + if (got_favicon && iconURI && browser === tabBrowser) { + got_favicon.resolve(iconURI); + got_favicon = null; + } + } + }; + gBrowser.addTabsProgressListener(listener); + + BrowserTestUtils.loadURI(tabBrowser, URI); + + let iconURI = yield got_favicon.promise; + is(iconURI, expectedIcon, "Correct icon before pushState."); + + got_favicon = Promise.defer(); + got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e); + yield ContentTask.spawn(tabBrowser, null, function() { + content.location.href += "#foo"; + }); + + // We've navigated and shouldn't get a call to onLinkIconAvailable. + TestUtils.executeSoon(() => { + got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser))); + }); + try { + yield got_favicon.promise; + } catch (e) { + iconURI = e; + } + is(iconURI, expectedIcon, "Correct icon after pushState."); + + gBrowser.removeTabsProgressListener(listener); + }); +}); + diff --git a/browser/base/content/test/general/browser_bug409481.js b/browser/base/content/test/general/browser_bug409481.js new file mode 100644 index 000000000..395ad93d4 --- /dev/null +++ b/browser/base/content/test/general/browser_bug409481.js @@ -0,0 +1,83 @@ +function test() { + waitForExplicitFinish(); + + // XXX This looks a bit odd, but is needed to avoid throwing when removing the + // event listeners below. See bug 310955. + document.getElementById("sidebar").addEventListener("load", delayedOpenUrl, true); + SidebarUI.show("viewWebPanelsSidebar"); +} + +function delayedOpenUrl() { + ok(true, "Ran delayedOpenUrl"); + setTimeout(openPanelUrl, 100); +} + +function openPanelUrl(event) { + ok(!document.getElementById("sidebar-box").hidden, "Sidebar showing"); + + var sidebar = document.getElementById("sidebar"); + var root = sidebar.contentDocument.documentElement; + ok(root.nodeName != "parsererror", "Sidebar is well formed"); + + sidebar.removeEventListener("load", delayedOpenUrl, true); + // XXX See comment above + sidebar.contentDocument.addEventListener("load", delayedRunTest, true); + var url = 'data:text/html,<div%20id="test_bug409481">Content!</div><a id="link" href="http://www.example.com/ctest">Link</a><input id="textbox">'; + sidebar.contentWindow.loadWebPanel(url); +} + +function delayedRunTest() { + ok(true, "Ran delayedRunTest"); + setTimeout(runTest, 100); +} + +function runTest(event) { + var sidebar = document.getElementById("sidebar"); + sidebar.contentDocument.removeEventListener("load", delayedRunTest, true); + + var browser = sidebar.contentDocument.getElementById("web-panels-browser"); + var div = browser && browser.contentDocument.getElementById("test_bug409481"); + ok(div && div.textContent == "Content!", "Sidebar content loaded"); + + var link = browser && browser.contentDocument.getElementById("link"); + sidebar.contentDocument.addEventListener("popupshown", contextMenuOpened, false); + + EventUtils.synthesizeMouseAtCenter(link, { type: "contextmenu", button: 2 }, browser.contentWindow); +} + +function contextMenuOpened() +{ + var sidebar = document.getElementById("sidebar"); + sidebar.contentDocument.removeEventListener("popupshown", contextMenuOpened, false); + + var copyLinkCommand = sidebar.contentDocument.getElementById("context-copylink"); + copyLinkCommand.addEventListener("command", copyLinkCommandExecuted, false); + copyLinkCommand.doCommand(); +} + +function copyLinkCommandExecuted(event) +{ + event.target.removeEventListener("command", copyLinkCommandExecuted, false); + + var sidebar = document.getElementById("sidebar"); + var browser = sidebar.contentDocument.getElementById("web-panels-browser"); + var textbox = browser && browser.contentDocument.getElementById("textbox"); + textbox.focus(); + document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste"); + is(textbox.value, "http://www.example.com/ctest", "copy link command"); + + sidebar.contentDocument.addEventListener("popuphidden", contextMenuClosed, false); + event.target.parentNode.hidePopup(); +} + +function contextMenuClosed() +{ + var sidebar = document.getElementById("sidebar"); + sidebar.contentDocument.removeEventListener("popuphidden", contextMenuClosed, false); + + SidebarUI.hide(); + + ok(document.getElementById("sidebar-box").hidden, "Sidebar successfully hidden"); + + finish(); +} diff --git a/browser/base/content/test/general/browser_bug409624.js b/browser/base/content/test/general/browser_bug409624.js new file mode 100644 index 000000000..8e46ec0c2 --- /dev/null +++ b/browser/base/content/test/general/browser_bug409624.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", + "resource://gre/modules/FormHistory.jsm"); + +add_task(function* test() { + // This test relies on the form history being empty to start with delete + // all the items first. + yield new Promise((resolve, reject) => { + FormHistory.update({ op: "remove" }, + { handleError(error) { + reject(error); + }, + handleCompletion(reason) { + if (!reason) { + resolve(); + } else { + reject(); + } + }, + }); + }); + + let prefService = Cc["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + + let tempScope = {}; + Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tempScope); + let Sanitizer = tempScope.Sanitizer; + let s = new Sanitizer(); + s.prefDomain = "privacy.cpd."; + let prefBranch = prefService.getBranch(s.prefDomain); + + prefBranch.setBoolPref("cache", false); + prefBranch.setBoolPref("cookies", false); + prefBranch.setBoolPref("downloads", false); + prefBranch.setBoolPref("formdata", true); + prefBranch.setBoolPref("history", false); + prefBranch.setBoolPref("offlineApps", false); + prefBranch.setBoolPref("passwords", false); + prefBranch.setBoolPref("sessions", false); + prefBranch.setBoolPref("siteSettings", false); + + // Sanitize now so we can test the baseline point. + yield s.sanitize(); + ok(!gFindBar.hasTransactions, "pre-test baseline for sanitizer"); + + gFindBar.getElement("findbar-textbox").value = "m"; + ok(gFindBar.hasTransactions, "formdata can be cleared after input"); + + yield s.sanitize(); + is(gFindBar.getElement("findbar-textbox").value, "", "findBar textbox should be empty after sanitize"); + ok(!gFindBar.hasTransactions, "No transactions after sanitize"); +}); diff --git a/browser/base/content/test/general/browser_bug413915.js b/browser/base/content/test/general/browser_bug413915.js new file mode 100644 index 000000000..86c94c427 --- /dev/null +++ b/browser/base/content/test/general/browser_bug413915.js @@ -0,0 +1,62 @@ +XPCOMUtils.defineLazyModuleGetter(this, "Feeds", + "resource:///modules/Feeds.jsm"); + +function test() { + var exampleUri = makeURI("http://example.com/"); + var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); + var principal = secman.createCodebasePrincipal(exampleUri, {}); + + function testIsFeed(aTitle, aHref, aType, aKnown) { + var link = { title: aTitle, href: aHref, type: aType }; + return Feeds.isValidFeed(link, principal, aKnown); + } + + var href = "http://example.com/feed/"; + var atomType = "application/atom+xml"; + var funkyAtomType = " aPPLICAtion/Atom+XML "; + var rssType = "application/rss+xml"; + var funkyRssType = " Application/RSS+XML "; + var rdfType = "application/rdf+xml"; + var texmlType = "text/xml"; + var appxmlType = "application/xml"; + var noRss = "Foo"; + var rss = "RSS"; + + // things that should be valid + ok(testIsFeed(noRss, href, atomType, false) == atomType, + "detect Atom feed"); + ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType, + "clean up and detect Atom feed"); + ok(testIsFeed(noRss, href, rssType, false) == rssType, + "detect RSS feed"); + ok(testIsFeed(noRss, href, funkyRssType, false) == rssType, + "clean up and detect RSS feed"); + + // things that should not be feeds + ok(testIsFeed(noRss, href, rdfType, false) == null, + "should not detect RDF non-feed"); + ok(testIsFeed(rss, href, rdfType, false) == null, + "should not detect RDF feed from type and title"); + ok(testIsFeed(noRss, href, texmlType, false) == null, + "should not detect text/xml non-feed"); + ok(testIsFeed(rss, href, texmlType, false) == null, + "should not detect text/xml feed from type and title"); + ok(testIsFeed(noRss, href, appxmlType, false) == null, + "should not detect application/xml non-feed"); + ok(testIsFeed(rss, href, appxmlType, false) == null, + "should not detect application/xml feed from type and title"); + + // security check only, returns cleaned up type or "application/rss+xml" + ok(testIsFeed(noRss, href, atomType, true) == atomType, + "feed security check should return Atom type"); + ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType, + "feed security check should return cleaned up Atom type"); + ok(testIsFeed(noRss, href, rssType, true) == rssType, + "feed security check should return RSS type"); + ok(testIsFeed(noRss, href, funkyRssType, true) == rssType, + "feed security check should return cleaned up RSS type"); + ok(testIsFeed(noRss, href, "", true) == rssType, + "feed security check without type should return RSS type"); + ok(testIsFeed(noRss, href, "garbage", true) == "garbage", + "feed security check with garbage type should return garbage"); +} diff --git a/browser/base/content/test/general/browser_bug416661.js b/browser/base/content/test/general/browser_bug416661.js new file mode 100644 index 000000000..a37971a34 --- /dev/null +++ b/browser/base/content/test/general/browser_bug416661.js @@ -0,0 +1,43 @@ +var tabElm, zoomLevel; +function start_test_prefNotSet() { + Task.spawn(function* () { + is(ZoomManager.zoom, 1, "initial zoom level should be 1"); + FullZoom.enlarge(); + + // capture the zoom level to test later + zoomLevel = ZoomManager.zoom; + isnot(zoomLevel, 1, "zoom level should have changed"); + + yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/general/moz.png"); + }).then(continue_test_prefNotSet, FullZoomHelper.failAndContinue(finish)); +} + +function continue_test_prefNotSet () { + Task.spawn(function* () { + is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image"); + yield FullZoom.reset(); + + yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/general/zoom_test.html"); + }).then(end_test_prefNotSet, FullZoomHelper.failAndContinue(finish)); +} + +function end_test_prefNotSet() { + Task.spawn(function* () { + is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted"); + + // Reset the zoom so that other tests have a fresh zoom level + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(); + finish(); + }); +} + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + tabElm = gBrowser.addTab(); + yield FullZoomHelper.selectTabAndWaitForLocationChange(tabElm); + yield FullZoomHelper.load(tabElm, "http://mochi.test:8888/browser/browser/base/content/test/general/zoom_test.html"); + }).then(start_test_prefNotSet, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug417483.js b/browser/base/content/test/general/browser_bug417483.js new file mode 100644 index 000000000..43ff7b917 --- /dev/null +++ b/browser/base/content/test/general/browser_bug417483.js @@ -0,0 +1,30 @@ +add_task(function* () { + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true); + const htmlContent = "data:text/html, <iframe src='data:text/html,text text'></iframe>"; + gBrowser.loadURI(htmlContent); + yield loadedPromise; + + yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) { + let frame = content.frames[0]; + let sel = frame.getSelection(); + let range = frame.document.createRange(); + let tn = frame.document.body.childNodes[0]; + range.setStart(tn, 4); + range.setEnd(tn, 5); + sel.addRange(range); + frame.focus(); + }); + + let contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + + let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouse("frame", 5, 5, + { type: "contextmenu", button: 2}, gBrowser.selectedBrowser); + yield popupShownPromise; + + ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces"); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden"); + contentAreaContextMenu.hidePopup(); + yield popupHiddenPromise; +}); diff --git a/browser/base/content/test/general/browser_bug419612.js b/browser/base/content/test/general/browser_bug419612.js new file mode 100644 index 000000000..8c34b2d39 --- /dev/null +++ b/browser/base/content/test/general/browser_bug419612.js @@ -0,0 +1,32 @@ +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + let testPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + let tab1 = gBrowser.addTab(); + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1); + yield FullZoomHelper.load(tab1, testPage); + + let tab2 = gBrowser.addTab(); + yield FullZoomHelper.load(tab2, testPage); + + FullZoom.enlarge(); + let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2); + let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser); + is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs"); + + gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false); + yield FullZoom.reset(); + gBrowser.selectedTab = tab1; + tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser); + tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser); + isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs"); + + if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs")) + gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs"); + yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1); + yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2); + }).then(finish, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug422590.js b/browser/base/content/test/general/browser_bug422590.js new file mode 100644 index 000000000..f26919cc5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug422590.js @@ -0,0 +1,50 @@ +function test() { + waitForExplicitFinish(); + // test the main (normal) browser window + testCustomize(window, testChromeless); +} + +function testChromeless() { + // test a chromeless window + var newWin = openDialog(getBrowserURL(), "_blank", + "chrome,dialog=no,location=yes,toolbar=no", "about:blank"); + ok(newWin, "got new window"); + + whenDelayedStartupFinished(newWin, function () { + // Check that the search bar is hidden + var searchBar = newWin.BrowserSearch.searchBar; + ok(searchBar, "got search bar"); + + var searchBarBO = searchBar.boxObject; + is(searchBarBO.width, 0, "search bar hidden"); + is(searchBarBO.height, 0, "search bar hidden"); + + testCustomize(newWin, function () { + newWin.close(); + finish(); + }); + }); +} + +function testCustomize(aWindow, aCallback) { + var fileMenu = aWindow.document.getElementById("file-menu"); + ok(fileMenu, "got file menu"); + is(fileMenu.disabled, false, "file menu initially enabled"); + + openToolbarCustomizationUI(function () { + // Can't use the property, since the binding may have since been removed + // if the element is hidden (see bug 422590) + is(fileMenu.getAttribute("disabled"), "true", + "file menu is disabled during toolbar customization"); + + closeToolbarCustomizationUI(onClose, aWindow); + }, aWindow); + + function onClose() { + is(fileMenu.getAttribute("disabled"), "false", + "file menu is enabled after toolbar customization"); + + if (aCallback) + aCallback(); + } +} diff --git a/browser/base/content/test/general/browser_bug423833.js b/browser/base/content/test/general/browser_bug423833.js new file mode 100644 index 000000000..d4069338b --- /dev/null +++ b/browser/base/content/test/general/browser_bug423833.js @@ -0,0 +1,138 @@ +/* Tests for proper behaviour of "Show this frame" context menu options */ + +// Two frames, one with text content, the other an error page +var invalidPage = 'http://127.0.0.1:55555/'; +var validPage = 'http://example.com/'; +var testPage = 'data:text/html,<frameset cols="400,400"><frame src="' + validPage + '"><frame src="' + invalidPage + '"></frameset>'; + +// Store the tab and window created in tests 2 and 3 respectively +var test2tab; +var test3window; + +// We use setInterval instead of setTimeout to avoid race conditions on error doc loads +var intervalID; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", test1Setup, true); + content.location = testPage; +} + +function test1Setup() { + if (content.frames.length < 2 || + content.frames[1].location != invalidPage) + // The error frame hasn't loaded yet + return; + + gBrowser.selectedBrowser.removeEventListener("load", test1Setup, true); + + var badFrame = content.frames[1]; + document.popupNode = badFrame.document.firstChild; + + var contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + var contextMenu = new nsContextMenu(contentAreaContextMenu); + + // We'd like to use another load listener here, but error pages don't fire load events + contextMenu.showOnlyThisFrame(); + intervalID = setInterval(testShowOnlyThisFrame, 3000); +} + +function testShowOnlyThisFrame() { + if (content.location.href == testPage) + // This is a stale event from the original page loading + return; + + // We should now have loaded the error page frame content directly + // in the tab, make sure the URL is right. + clearInterval(intervalID); + + is(content.location.href, invalidPage, "Should navigate to page url, not about:neterror"); + + // Go back to the frames page + gBrowser.addEventListener("load", test2Setup, true); + content.location = testPage; +} + +function test2Setup() { + if (content.frames.length < 2 || + content.frames[1].location != invalidPage) + // The error frame hasn't loaded yet + return; + + gBrowser.removeEventListener("load", test2Setup, true); + + // Now let's do the whole thing again, but this time for "Open frame in new tab" + var badFrame = content.frames[1]; + + document.popupNode = badFrame.document.firstChild; + + var contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + var contextMenu = new nsContextMenu(contentAreaContextMenu); + + gBrowser.tabContainer.addEventListener("TabOpen", function (event) { + test2tab = event.target; + gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, false); + }, false); + contextMenu.openFrameInTab(); + ok(test2tab, "openFrameInTab() opened a tab"); + + gBrowser.selectedTab = test2tab; + + intervalID = setInterval(testOpenFrameInTab, 3000); +} + +function testOpenFrameInTab() { + if (gBrowser.contentDocument.location.href == "about:blank") + // Wait another cycle + return; + + clearInterval(intervalID); + + // We should now have the error page in a new, active tab. + is(gBrowser.contentDocument.location.href, invalidPage, "New tab should have page url, not about:neterror"); + + // Clear up the new tab, and punt to test 3 + gBrowser.removeCurrentTab(); + + test3Setup(); +} + +function test3Setup() { + // One more time, for "Open frame in new window" + var badFrame = content.frames[1]; + document.popupNode = badFrame.document.firstChild; + + var contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + var contextMenu = new nsContextMenu(contentAreaContextMenu); + + Services.ww.registerNotification(function (aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") + test3window = aSubject; + Services.ww.unregisterNotification(arguments.callee); + }); + + contextMenu.openFrame(); + + intervalID = setInterval(testOpenFrame, 3000); +} + +function testOpenFrame() { + if (!test3window || test3window.content.location.href == "about:blank") { + info("testOpenFrame: Wait another cycle"); + return; + } + + clearInterval(intervalID); + + is(test3window.content.location.href, invalidPage, "New window should have page url, not about:neterror"); + + test3window.close(); + cleanup(); +} + +function cleanup() { + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/base/content/test/general/browser_bug424101.js b/browser/base/content/test/general/browser_bug424101.js new file mode 100644 index 000000000..8000d2ae9 --- /dev/null +++ b/browser/base/content/test/general/browser_bug424101.js @@ -0,0 +1,52 @@ +/* Make sure that the context menu appears on form elements */ + +add_task(function *() { + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,test"); + + let contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + + let tests = [ + { element: "input", type: "text" }, + { element: "input", type: "password" }, + { element: "input", type: "image" }, + { element: "input", type: "button" }, + { element: "input", type: "submit" }, + { element: "input", type: "reset" }, + { element: "input", type: "checkbox" }, + { element: "input", type: "radio" }, + { element: "button" }, + { element: "select" }, + { element: "option" }, + { element: "optgroup" } + ]; + + for (let index = 0; index < tests.length; index++) { + let test = tests[index]; + + yield ContentTask.spawn(gBrowser.selectedBrowser, + { element: test.element, type: test.type, index: index }, + function* (arg) { + let element = content.document.createElement(arg.element); + element.id = "element" + arg.index; + if (arg.type) { + element.setAttribute("type", arg.type); + } + content.document.body.appendChild(element); + }); + + let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouseAtCenter("#element" + index, + { type: "contextmenu", button: 2}, gBrowser.selectedBrowser); + yield popupShownPromise; + + let typeAttr = test.type ? "type=" + test.type + " " : ""; + is(gContextMenu.shouldDisplay, true, + "context menu behavior for <" + test.element + " " + typeAttr + "> is wrong"); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden"); + contentAreaContextMenu.hidePopup(); + yield popupHiddenPromise; + } + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug427559.js b/browser/base/content/test/general/browser_bug427559.js new file mode 100644 index 000000000..78cecdefa --- /dev/null +++ b/browser/base/content/test/general/browser_bug427559.js @@ -0,0 +1,38 @@ +"use strict"; + +/* + * Test bug 427559 to make sure focused elements that are no longer on the page + * will have focus transferred to the window when changing tabs back to that + * tab with the now-gone element. + */ + +// Default focus on a button and have it kill itself on blur. +const URL = 'data:text/html;charset=utf-8,' + + '<body><button onblur="this.remove()">' + + '<script>document.body.firstChild.focus()</script></body>'; + +function getFocusedLocalName(browser) { + return ContentTask.spawn(browser, null, function* () { + return content.document.activeElement.localName; + }); +} + +add_task(function* () { + let testTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + + let browser = testTab.linkedBrowser; + + is((yield getFocusedLocalName(browser)), "button", "button is focused"); + + let blankTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + + yield BrowserTestUtils.switchTab(gBrowser, testTab); + + // Make sure focus is given to the window because the element is now gone. + is((yield getFocusedLocalName(browser)), "body", "body is focused"); + + // Cleanup. + gBrowser.removeTab(blankTab); + gBrowser.removeCurrentTab(); + +}); diff --git a/browser/base/content/test/general/browser_bug431826.js b/browser/base/content/test/general/browser_bug431826.js new file mode 100644 index 000000000..592ea9cef --- /dev/null +++ b/browser/base/content/test/general/browser_bug431826.js @@ -0,0 +1,50 @@ +function remote(task) { + return ContentTask.spawn(gBrowser.selectedBrowser, null, task); +} + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + + let promise = remote(function () { + return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true, event => { + return content.document.documentURI != "about:blank"; + }).then(() => 0); // don't want to send the event to the chrome process + }); + gBrowser.loadURI("https://nocert.example.com/"); + yield promise; + + yield remote(() => { + // Confirm that we are displaying the contributed error page, not the default + let uri = content.document.documentURI; + Assert.ok(uri.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror"); + }); + + yield remote(() => { + let div = content.document.getElementById("badCertAdvancedPanel"); + // Confirm that the expert section is collapsed + Assert.ok(div, "Advanced content div should exist"); + Assert.equal(div.ownerGlobal.getComputedStyle(div).display, + "none", "Advanced content should not be visible by default"); + }); + + // Tweak the expert mode pref + gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true); + + promise = remote(function () { + return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true); + }); + gBrowser.reload(); + yield promise; + + yield remote(() => { + let div = content.document.getElementById("badCertAdvancedPanel"); + Assert.ok(div, "Advanced content div should exist"); + Assert.equal(div.ownerGlobal.getComputedStyle(div).display, + "block", "Advanced content should be visible by default"); + }); + + // Clean up + gBrowser.removeCurrentTab(); + if (gPrefService.prefHasUserValue("browser.xul.error_pages.expert_bad_cert")) + gPrefService.clearUserPref("browser.xul.error_pages.expert_bad_cert"); +}); diff --git a/browser/base/content/test/general/browser_bug432599.js b/browser/base/content/test/general/browser_bug432599.js new file mode 100644 index 000000000..a5f7c0b5e --- /dev/null +++ b/browser/base/content/test/general/browser_bug432599.js @@ -0,0 +1,127 @@ +function invokeUsingCtrlD(phase) { + switch (phase) { + case 1: + EventUtils.synthesizeKey("d", { accelKey: true }); + break; + case 2: + case 4: + EventUtils.synthesizeKey("VK_ESCAPE", {}); + break; + case 3: + EventUtils.synthesizeKey("d", { accelKey: true }); + EventUtils.synthesizeKey("d", { accelKey: true }); + break; + } +} + +function invokeUsingStarButton(phase) { + switch (phase) { + case 1: + EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star, {}); + break; + case 2: + case 4: + EventUtils.synthesizeKey("VK_ESCAPE", {}); + break; + case 3: + EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star, + { clickCount: 2 }); + break; + } +} + +var testURL = "data:text/plain,Content"; +var bookmarkId; + +function add_bookmark(aURI, aTitle) { + return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, + aTitle); +} + +// test bug 432599 +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + waitForStarChange(false, initTest); + }, true); + + content.location = testURL; +} + +function initTest() { + // First, bookmark the page. + bookmarkId = add_bookmark(makeURI(testURL), "Bug 432599 Test"); + + checkBookmarksPanel(invokers[currentInvoker], 1); +} + +function waitForStarChange(aValue, aCallback) { + let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED + : BookmarkingUI.STATUS_UNSTARRED; + if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING || + BookmarkingUI.status != expectedStatus) { + info("Waiting for star button change."); + setTimeout(waitForStarChange, 50, aValue, aCallback); + return; + } + aCallback(); +} + +var invokers = [invokeUsingStarButton, invokeUsingCtrlD]; +var currentInvoker = 0; + +var initialValue; +var initialRemoveHidden; + +var popupElement = document.getElementById("editBookmarkPanel"); +var titleElement = document.getElementById("editBookmarkPanelTitle"); +var removeElement = document.getElementById("editBookmarkPanelRemoveButton"); + +function checkBookmarksPanel(invoker, phase) +{ + let onPopupShown = function(aEvent) { + if (aEvent.originalTarget == popupElement) { + popupElement.removeEventListener("popupshown", arguments.callee, false); + checkBookmarksPanel(invoker, phase + 1); + } + }; + let onPopupHidden = function(aEvent) { + if (aEvent.originalTarget == popupElement) { + popupElement.removeEventListener("popuphidden", arguments.callee, false); + if (phase < 4) { + checkBookmarksPanel(invoker, phase + 1); + } else { + ++currentInvoker; + if (currentInvoker < invokers.length) { + checkBookmarksPanel(invokers[currentInvoker], 1); + } else { + gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true}); + PlacesUtils.bookmarks.removeItem(bookmarkId); + executeSoon(finish); + } + } + } + }; + + switch (phase) { + case 1: + case 3: + popupElement.addEventListener("popupshown", onPopupShown, false); + break; + case 2: + popupElement.addEventListener("popuphidden", onPopupHidden, false); + initialValue = titleElement.value; + initialRemoveHidden = removeElement.hidden; + break; + case 4: + popupElement.addEventListener("popuphidden", onPopupHidden, false); + is(titleElement.value, initialValue, "The bookmark panel's title should be the same"); + is(removeElement.hidden, initialRemoveHidden, "The bookmark panel's visibility should not change"); + break; + } + invoker(phase); +} diff --git a/browser/base/content/test/general/browser_bug435035.js b/browser/base/content/test/general/browser_bug435035.js new file mode 100644 index 000000000..7570ef0d7 --- /dev/null +++ b/browser/base/content/test/general/browser_bug435035.js @@ -0,0 +1,17 @@ +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + is(document.getElementById("identity-box").className, + "unknownIdentity mixedDisplayContent", + "identity box has class name for mixed content"); + + gBrowser.removeCurrentTab(); + finish(); + }); + + gBrowser.loadURI( + "https://example.com/browser/browser/base/content/test/general/test_bug435035.html" + ); +} diff --git a/browser/base/content/test/general/browser_bug435325.js b/browser/base/content/test/general/browser_bug435325.js new file mode 100644 index 000000000..2ae15deff --- /dev/null +++ b/browser/base/content/test/general/browser_bug435325.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */ + +var proxyPrefValue; + +function test() { + waitForExplicitFinish(); + + // Go offline and disable the proxy and cache, then try to load the test URL. + Services.io.offline = true; + + // Tests always connect to localhost, and per bug 87717, localhost is now + // reachable in offline mode. To avoid this, disable any proxy. + proxyPrefValue = Services.prefs.getIntPref("network.proxy.type"); + Services.prefs.setIntPref("network.proxy.type", 0); + + Services.prefs.setBoolPref("browser.cache.disk.enable", false); + Services.prefs.setBoolPref("browser.cache.memory.enable", false); + + gBrowser.selectedTab = gBrowser.addTab("http://example.com/"); + + let contentScript = ` + let listener = function () { + removeEventListener("DOMContentLoaded", listener); + sendAsyncMessage("Test:DOMContentLoaded", { uri: content.document.documentURI }); + }; + addEventListener("DOMContentLoaded", listener); + `; + + function pageloaded({ data }) { + mm.removeMessageListener("Test:DOMContentLoaded", pageloaded); + checkPage(data); + } + + let mm = gBrowser.selectedBrowser.messageManager; + mm.addMessageListener("Test:DOMContentLoaded", pageloaded); + mm.loadFrameScript("data:," + contentScript, true); +} + +function checkPage(data) { + ok(Services.io.offline, "Setting Services.io.offline to true."); + + is(data.uri.substring(0, 27), + "about:neterror?e=netOffline", "Loading the Offline mode neterror page."); + + // Re-enable the proxy so example.com is resolved to localhost, rather than + // the actual example.com. + Services.prefs.setIntPref("network.proxy.type", proxyPrefValue); + + Services.obs.addObserver(function observer(aSubject, aTopic) { + ok(!Services.io.offline, "After clicking the Try Again button, we're back " + + "online."); + Services.obs.removeObserver(observer, "network:offline-status-changed", false); + finish(); + }, "network:offline-status-changed", false); + + ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + content.document.getElementById("errorTryAgain").click(); + }); +} + +registerCleanupFunction(function() { + Services.prefs.setBoolPref("browser.cache.disk.enable", true); + Services.prefs.setBoolPref("browser.cache.memory.enable", true); + Services.io.offline = false; + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug441778.js b/browser/base/content/test/general/browser_bug441778.js new file mode 100644 index 000000000..fa938541f --- /dev/null +++ b/browser/base/content/test/general/browser_bug441778.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test the fix for bug 441778 to ensure site-specific page zoom doesn't get + * modified by sub-document loads of content from a different domain. + */ + +function test() { + waitForExplicitFinish(); + + const TEST_PAGE_URL = 'data:text/html,<body><iframe src=""></iframe></body>'; + const TEST_IFRAME_URL = "http://test2.example.org/"; + + Task.spawn(function* () { + // Prepare the test tab + let tab = gBrowser.addTab(); + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab); + + let testBrowser = tab.linkedBrowser; + + yield FullZoomHelper.load(tab, TEST_PAGE_URL); + + // Change the zoom level and then save it so we can compare it to the level + // after loading the sub-document. + FullZoom.enlarge(); + var zoomLevel = ZoomManager.zoom; + + // Start the sub-document load. + let deferred = Promise.defer(); + executeSoon(function () { + BrowserTestUtils.browserLoaded(testBrowser, true).then(url => { + is(url, TEST_IFRAME_URL, "got the load event for the iframe"); + is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load"); + + FullZoomHelper.removeTabAndWaitForLocationChange(). + then(() => deferred.resolve()); + }); + ContentTask.spawn(testBrowser, TEST_IFRAME_URL, url => { + content.document.querySelector("iframe").src = url; + }); + }); + yield deferred.promise; + }).then(finish, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug455852.js b/browser/base/content/test/general/browser_bug455852.js new file mode 100644 index 000000000..ce883b581 --- /dev/null +++ b/browser/base/content/test/general/browser_bug455852.js @@ -0,0 +1,20 @@ +add_task(function*() { + is(gBrowser.tabs.length, 1, "one tab is open"); + + gBrowser.selectedBrowser.focus(); + isnot(document.activeElement, gURLBar.inputField, "location bar is not focused"); + + var tab = gBrowser.selectedTab; + gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false); + + let tabClosedPromise = BrowserTestUtils.removeTab(tab, {dontRemove: true}); + EventUtils.synthesizeKey("w", { accelKey: true }); + yield tabClosedPromise; + + is(tab.parentNode, null, "ctrl+w removes the tab"); + is(gBrowser.tabs.length, 1, "a new tab has been opened"); + is(document.activeElement, gURLBar.inputField, "location bar is focused for the new tab"); + + if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab")) + gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab"); +}); diff --git a/browser/base/content/test/general/browser_bug460146.js b/browser/base/content/test/general/browser_bug460146.js new file mode 100644 index 000000000..1fdf0921c --- /dev/null +++ b/browser/base/content/test/general/browser_bug460146.js @@ -0,0 +1,51 @@ +/* Check proper image url retrieval from all kinds of elements/styles */ + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + + gBrowser.selectedBrowser.addEventListener("load", function () { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + + var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec, + "mediaTab"); + + pageInfo.addEventListener("load", function () { + pageInfo.removeEventListener("load", arguments.callee, true); + pageInfo.onFinished.push(function () { + executeSoon(function () { + var imageTree = pageInfo.document.getElementById("imagetree"); + var imageRowsNum = imageTree.view.rowCount; + + ok(imageTree, "Image tree is null (media tab is broken)"); + + ok(imageRowsNum == 7, "Number of images listed: " + + imageRowsNum + ", should be 7"); + + pageInfo.close(); + gBrowser.removeCurrentTab(); + finish(); + }); + }); + }, true); + }, true); + + content.location = + "data:text/html," + + "<html>" + + " <head>" + + " <title>Test for media tab</title>" + + " <link rel='shortcut icon' href='file:///dummy_icon.ico'>" + // Icon + " </head>" + + " <body style='background-image:url(about:logo?a);'>" + // Background + " <img src='file:///dummy_image.gif'>" + // Image + " <ul>" + + " <li style='list-style:url(about:logo?b);'>List Item 1</li>" + // Bullet + " </ul> " + + " <div style='-moz-border-image: url(about:logo?c) 20 20 20 20;'>test</div>" + // Border + " <a href='' style='cursor: url(about:logo?d),default;'>test link</a>" + // Cursor + " <object type='image/svg+xml' width=20 height=20 data='file:///dummy_object.svg'></object>" + // Object + " </body>" + + "</html>"; +} diff --git a/browser/base/content/test/general/browser_bug462289.js b/browser/base/content/test/general/browser_bug462289.js new file mode 100644 index 000000000..1ce79f07e --- /dev/null +++ b/browser/base/content/test/general/browser_bug462289.js @@ -0,0 +1,81 @@ +var tab1, tab2; + +function focus_in_navbar() +{ + var parent = document.activeElement.parentNode; + while (parent && parent.id != "nav-bar") + parent = parent.parentNode; + + return parent != null; +} + +function test() +{ + waitForExplicitFinish(); + + tab1 = gBrowser.addTab("about:blank", {skipAnimation: true}); + tab2 = gBrowser.addTab("about:blank", {skipAnimation: true}); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + executeSoon(step2); +} + +function step2() +{ + is(gBrowser.selectedTab, tab1, "1st click on tab1 selects tab"); + isnot(document.activeElement, tab1, "1st click on tab1 does not activate tab"); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + executeSoon(step3); +} + +function step3() +{ + is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected"); + isnot(document.activeElement, tab1, "2nd click on selected tab1 does not activate tab"); + + ok(true, "focusing URLBar then sending 1 Shift+Tab."); + gURLBar.focus(); + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}); + is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected"); + is(document.activeElement, tab1, "tab key to selected tab1 activates tab"); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + executeSoon(step4); +} + +function step4() +{ + is(gBrowser.selectedTab, tab1, "3rd click on activated tab1 keeps tab selected"); + is(document.activeElement, tab1, "3rd click on activated tab1 keeps tab activated"); + + gBrowser.addEventListener("TabSwitchDone", step5); + EventUtils.synthesizeMouseAtCenter(tab2, {}); +} + +function step5() +{ + gBrowser.removeEventListener("TabSwitchDone", step5); + + // The tabbox selects a tab within a setTimeout in a bubbling mousedown event + // listener, and focuses the current tab if another tab previously had focus. + is(gBrowser.selectedTab, tab2, "click on tab2 while tab1 is activated selects tab"); + is(document.activeElement, tab2, "click on tab2 while tab1 is activated activates tab"); + + info("focusing content then sending middle-button mousedown to tab2."); + gBrowser.selectedBrowser.focus(); + + EventUtils.synthesizeMouseAtCenter(tab2, {button: 1, type: "mousedown"}); + executeSoon(step6); +} + +function step6() +{ + is(gBrowser.selectedTab, tab2, "middle-button mousedown on selected tab2 keeps tab selected"); + isnot(document.activeElement, tab2, "middle-button mousedown on selected tab2 does not activate tab"); + + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab1); + + finish(); +} diff --git a/browser/base/content/test/general/browser_bug462673.js b/browser/base/content/test/general/browser_bug462673.js new file mode 100644 index 000000000..f5b090917 --- /dev/null +++ b/browser/base/content/test/general/browser_bug462673.js @@ -0,0 +1,36 @@ +add_task(function* () { + var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); + yield SimpleTest.promiseFocus(win); + + let tab = win.gBrowser.tabContainer.firstChild; + yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html"); + + is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab"); + is(win.gBrowser.selectedTab, tab.nextSibling, "dependent tab is selected"); + win.gBrowser.removeTab(tab); + + // Closing a tab will also close its parent chrome window, but async + yield promiseWindowWillBeClosed(win); +}); + +add_task(function* () { + var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); + yield SimpleTest.promiseFocus(win); + + let tab = win.gBrowser.tabContainer.firstChild; + yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html"); + + var newTab = win.gBrowser.addTab(); + var newBrowser = newTab.linkedBrowser; + win.gBrowser.removeTab(tab); + ok(!win.closed, "Window stays open"); + if (!win.closed) { + is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab"); + is(win.gBrowser.browsers.length, 1, "Window has one browser"); + is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected"); + is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected"); + is(win.gBrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected"); + } + + yield promiseWindowClosed(win); +}); diff --git a/browser/base/content/test/general/browser_bug477014.js b/browser/base/content/test/general/browser_bug477014.js new file mode 100644 index 000000000..8a0fac6d8 --- /dev/null +++ b/browser/base/content/test/general/browser_bug477014.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// That's a gecko! +const iconURLSpec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; +var testPage="data:text/plain,test bug 477014"; + +add_task(function*() { + let tabToDetach = gBrowser.addTab(testPage); + yield waitForDocLoadComplete(tabToDetach.linkedBrowser); + + gBrowser.setIcon(tabToDetach, iconURLSpec, + Services.scriptSecurityManager.getSystemPrincipal()); + tabToDetach.setAttribute("busy", "true"); + + // detach and set the listener on the new window + let newWindow = gBrowser.replaceTabWithWindow(tabToDetach); + yield promiseWaitForEvent(tabToDetach.linkedBrowser, "SwapDocShells"); + + is(newWindow.gBrowser.selectedTab.hasAttribute("busy"), true, "Busy attribute should be correct"); + is(newWindow.gBrowser.getIcon(), iconURLSpec, "Icon should be correct"); + + newWindow.close(); +}); diff --git a/browser/base/content/test/general/browser_bug479408.js b/browser/base/content/test/general/browser_bug479408.js new file mode 100644 index 000000000..0dfa96f2e --- /dev/null +++ b/browser/base/content/test/general/browser_bug479408.js @@ -0,0 +1,17 @@ +function test() { + waitForExplicitFinish(); + let tab = gBrowser.selectedTab = gBrowser.addTab( + "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug479408_sample.html"); + + gBrowser.addEventListener("DOMLinkAdded", function(aEvent) { + gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, true); + + executeSoon(function() { + ok(!tab.linkedBrowser.engines, + "the subframe's search engine wasn't detected"); + + gBrowser.removeTab(tab); + finish(); + }); + }, true); +} diff --git a/browser/base/content/test/general/browser_bug479408_sample.html b/browser/base/content/test/general/browser_bug479408_sample.html new file mode 100644 index 000000000..f83f02bb9 --- /dev/null +++ b/browser/base/content/test/general/browser_bug479408_sample.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<title>Testcase for bug 479408</title> + +<iframe src='data:text/html,<link%20rel="search"%20type="application/opensearchdescription+xml"%20title="Search%20bug%20479408"%20href="http://example.com/search.xml">'> diff --git a/browser/base/content/test/general/browser_bug481560.js b/browser/base/content/test/general/browser_bug481560.js new file mode 100644 index 000000000..bb9249e75 --- /dev/null +++ b/browser/base/content/test/general/browser_bug481560.js @@ -0,0 +1,21 @@ +function test() { + waitForExplicitFinish(); + + whenNewWindowLoaded(null, function (win) { + waitForFocus(function () { + function onTabClose() { + ok(false, "shouldn't have gotten the TabClose event for the last tab"); + } + var tab = win.gBrowser.selectedTab; + tab.addEventListener("TabClose", onTabClose, false); + + EventUtils.synthesizeKey("w", { accelKey: true }, win); + + ok(win.closed, "accel+w closed the window immediately"); + + tab.removeEventListener("TabClose", onTabClose, false); + + finish(); + }, win); + }); +} diff --git a/browser/base/content/test/general/browser_bug484315.js b/browser/base/content/test/general/browser_bug484315.js new file mode 100644 index 000000000..fb23ae33a --- /dev/null +++ b/browser/base/content/test/general/browser_bug484315.js @@ -0,0 +1,23 @@ +function test() { + var contentWin = window.open("about:blank", "", "width=100,height=100"); + var enumerator = Services.wm.getEnumerator("navigator:browser"); + + while (enumerator.hasMoreElements()) { + let win = enumerator.getNext(); + if (win.content == contentWin) { + gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false); + win.gBrowser.removeCurrentTab(); + ok(win.closed, "popup is closed"); + + // clean up + if (!win.closed) + win.close(); + if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab")) + gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab"); + + return; + } + } + + throw "couldn't find the content window"; +} diff --git a/browser/base/content/test/general/browser_bug491431.js b/browser/base/content/test/general/browser_bug491431.js new file mode 100644 index 000000000..d270e912e --- /dev/null +++ b/browser/base/content/test/general/browser_bug491431.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testPage = "data:text/plain,test bug 491431 Page"; + +function test() { + waitForExplicitFinish(); + + let newWin, tabA, tabB; + + // test normal close + tabA = gBrowser.addTab(testPage); + gBrowser.tabContainer.addEventListener("TabClose", function(firstTabCloseEvent) { + gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true); + ok(!firstTabCloseEvent.detail.adoptedBy, "This was a normal tab close"); + + // test tab close by moving + tabB = gBrowser.addTab(testPage); + gBrowser.tabContainer.addEventListener("TabClose", function(secondTabCloseEvent) { + gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true); + executeSoon(function() { + ok(secondTabCloseEvent.detail.adoptedBy, "This was a tab closed by moving"); + + // cleanup + newWin.close(); + executeSoon(finish); + }); + }, true); + newWin = gBrowser.replaceTabWithWindow(tabB); + }, true); + gBrowser.removeTab(tabA); +} + diff --git a/browser/base/content/test/general/browser_bug495058.js b/browser/base/content/test/general/browser_bug495058.js new file mode 100644 index 000000000..a82c6c931 --- /dev/null +++ b/browser/base/content/test/general/browser_bug495058.js @@ -0,0 +1,38 @@ +/** + * Tests that the right elements of a tab are focused when it is + * torn out into its own window. + */ + +const URIS = [ + "about:blank", + "about:sessionrestore", + "about:privatebrowsing", +]; + +add_task(function*() { + for (let uri of URIS) { + let tab = gBrowser.addTab(); + yield BrowserTestUtils.loadURI(tab.linkedBrowser, uri); + + let win = gBrowser.replaceTabWithWindow(tab); + yield TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == win); + tab = win.gBrowser.selectedTab; + + // BrowserTestUtils doesn't get the add-on shims, which means that + // MozAfterPaint won't get shimmed over if we add an event handler + // for it in the parent. + if (tab.linkedBrowser.isRemoteBrowser) { + yield BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "MozAfterPaint"); + } else { + yield BrowserTestUtils.waitForEvent(tab.linkedBrowser, "MozAfterPaint"); + } + + Assert.equal(win.gBrowser.currentURI.spec, uri, uri + ": uri loaded in detached tab"); + Assert.equal(win.document.activeElement, win.gBrowser.selectedBrowser, uri + ": browser is focused"); + Assert.equal(win.gURLBar.value, "", uri + ": urlbar is empty"); + Assert.ok(win.gURLBar.placeholder, uri + ": placeholder text is present"); + + yield BrowserTestUtils.closeWindow(win); + } +}); diff --git a/browser/base/content/test/general/browser_bug517902.js b/browser/base/content/test/general/browser_bug517902.js new file mode 100644 index 000000000..bc1d16f4b --- /dev/null +++ b/browser/base/content/test/general/browser_bug517902.js @@ -0,0 +1,42 @@ +/* Make sure that "View Image Info" loads the correct image data */ + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + + gBrowser.selectedBrowser.addEventListener("load", function () { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + + var doc = gBrowser.contentDocument; + var testImg = doc.getElementById("test-image"); + var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec, + "mediaTab", testImg); + + pageInfo.addEventListener("load", function () { + pageInfo.removeEventListener("load", arguments.callee, true); + pageInfo.onFinished.push(function () { + executeSoon(function () { + var pageInfoImg = pageInfo.document.getElementById("thepreviewimage"); + + is(pageInfoImg.src, testImg.src, "selected image has the correct source"); + is(pageInfoImg.width, testImg.width, "selected image has the correct width"); + is(pageInfoImg.height, testImg.height, "selected image has the correct height"); + + pageInfo.close(); + gBrowser.removeCurrentTab(); + finish(); + }); + }); + }, true); + }, true); + + content.location = + "data:text/html," + + "<style type='text/css'>%23test-image,%23not-test-image {background-image: url('about:logo?c');}</style>" + + "<img src='about:logo?b' height=300 width=350 alt=2 id='not-test-image'>" + + "<img src='about:logo?b' height=300 width=350 alt=2>" + + "<img src='about:logo?a' height=200 width=250>" + + "<img src='about:logo?b' height=200 width=250 alt=1>" + + "<img src='about:logo?b' height=100 width=150 alt=2 id='test-image'>"; +} diff --git a/browser/base/content/test/general/browser_bug519216.js b/browser/base/content/test/general/browser_bug519216.js new file mode 100644 index 000000000..d3a517086 --- /dev/null +++ b/browser/base/content/test/general/browser_bug519216.js @@ -0,0 +1,45 @@ +function test() { + waitForExplicitFinish(); + gBrowser.addProgressListener(progressListener1); + gBrowser.addProgressListener(progressListener2); + gBrowser.addProgressListener(progressListener3); + gBrowser.loadURI("data:text/plain,bug519216"); +} + +var calledListener1 = false; +var progressListener1 = { + onLocationChange: function onLocationChange() { + calledListener1 = true; + gBrowser.removeProgressListener(this); + } +}; + +var calledListener2 = false; +var progressListener2 = { + onLocationChange: function onLocationChange() { + ok(calledListener1, "called progressListener1 before progressListener2"); + calledListener2 = true; + gBrowser.removeProgressListener(this); + } +}; + +var progressListener3 = { + onLocationChange: function onLocationChange() { + ok(calledListener2, "called progressListener2 before progressListener3"); + gBrowser.removeProgressListener(this); + gBrowser.addProgressListener(progressListener4); + executeSoon(function () { + expectListener4 = true; + gBrowser.reload(); + }); + } +}; + +var expectListener4 = false; +var progressListener4 = { + onLocationChange: function onLocationChange() { + ok(expectListener4, "didn't call progressListener4 for the first location change"); + gBrowser.removeProgressListener(this); + executeSoon(finish); + } +}; diff --git a/browser/base/content/test/general/browser_bug520538.js b/browser/base/content/test/general/browser_bug520538.js new file mode 100644 index 000000000..e0b64db9d --- /dev/null +++ b/browser/base/content/test/general/browser_bug520538.js @@ -0,0 +1,15 @@ +function test() { + var tabCount = gBrowser.tabs.length; + gBrowser.selectedBrowser.focus(); + browserDOMWindow.openURI(makeURI("about:blank"), + null, + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); + is(gBrowser.tabs.length, tabCount + 1, + "'--new-tab about:blank' opens a new tab"); + is(gBrowser.selectedTab, gBrowser.tabs[tabCount], + "'--new-tab about:blank' selects the new tab"); + is(document.activeElement, gURLBar.inputField, + "'--new-tab about:blank' focuses the location bar"); + gBrowser.removeCurrentTab(); +} diff --git a/browser/base/content/test/general/browser_bug521216.js b/browser/base/content/test/general/browser_bug521216.js new file mode 100644 index 000000000..735ae92f6 --- /dev/null +++ b/browser/base/content/test/general/browser_bug521216.js @@ -0,0 +1,50 @@ +var expected = ["TabOpen", "onStateChange", "onLocationChange", "onLinkIconAvailable"]; +var actual = []; +var tabIndex = -1; +this.__defineGetter__("tab", () => gBrowser.tabs[tabIndex]); + +function test() { + waitForExplicitFinish(); + tabIndex = gBrowser.tabs.length; + gBrowser.addTabsProgressListener(progressListener); + gBrowser.tabContainer.addEventListener("TabOpen", TabOpen, false); + gBrowser.addTab("data:text/html,<html><head><link href='about:logo' rel='shortcut icon'>"); +} + +function record(aName) { + info("got " + aName); + if (actual.indexOf(aName) == -1) + actual.push(aName); + if (actual.length == expected.length) { + is(actual.toString(), expected.toString(), + "got events and progress notifications in expected order"); + + executeSoon(function(tab) { + gBrowser.removeTab(tab); + gBrowser.removeTabsProgressListener(progressListener); + gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false); + finish(); + }.bind(null, tab)); + } +} + +function TabOpen(aEvent) { + if (aEvent.target == tab) + record(arguments.callee.name); +} + +var progressListener = { + onLocationChange: function onLocationChange(aBrowser) { + if (aBrowser == tab.linkedBrowser) + record(arguments.callee.name); + }, + onStateChange: function onStateChange(aBrowser) { + if (aBrowser == tab.linkedBrowser) + record(arguments.callee.name); + }, + onLinkIconAvailable: function onLinkIconAvailable(aBrowser, aIconURL) { + if (aBrowser == tab.linkedBrowser && + aIconURL == "about:logo") + record(arguments.callee.name); + } +}; diff --git a/browser/base/content/test/general/browser_bug533232.js b/browser/base/content/test/general/browser_bug533232.js new file mode 100644 index 000000000..6c7a0e51f --- /dev/null +++ b/browser/base/content/test/general/browser_bug533232.js @@ -0,0 +1,36 @@ +function test() { + var tab1 = gBrowser.selectedTab; + var tab2 = gBrowser.addTab(); + var childTab1; + var childTab2; + + childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true }); + gBrowser.selectedTab = childTab1; + gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true }); + is(idx(gBrowser.selectedTab), idx(tab1), + "closing a tab next to its parent selects the parent"); + + childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true }); + gBrowser.selectedTab = tab2; + gBrowser.selectedTab = childTab1; + gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true }); + is(idx(gBrowser.selectedTab), idx(tab2), + "closing a tab next to its parent doesn't select the parent if another tab had been selected ad interim"); + + gBrowser.selectedTab = tab1; + childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true }); + childTab2 = gBrowser.addTab("about:blank", { relatedToCurrent: true }); + gBrowser.selectedTab = childTab1; + gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true }); + is(idx(gBrowser.selectedTab), idx(childTab2), + "closing a tab next to its parent selects the next tab with the same parent"); + gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true }); + is(idx(gBrowser.selectedTab), idx(tab2), + "closing the last tab in a set of child tabs doesn't go back to the parent"); + + gBrowser.removeTab(tab2, { skipPermitUnload: true }); +} + +function idx(tab) { + return Array.indexOf(gBrowser.tabs, tab); +} diff --git a/browser/base/content/test/general/browser_bug537013.js b/browser/base/content/test/general/browser_bug537013.js new file mode 100644 index 000000000..5ae1586ea --- /dev/null +++ b/browser/base/content/test/general/browser_bug537013.js @@ -0,0 +1,135 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Tests for bug 537013 to ensure proper tab-sequestration of find bar. */ + +var tabs = []; +var texts = [ + "This side up.", + "The world is coming to an end. Please log off.", + "Klein bottle for sale. Inquire within.", + "To err is human; to forgive is not company policy." +]; + +var Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard); +var HasFindClipboard = Clipboard.supportsFindClipboard(); + +function addTabWithText(aText, aCallback) { + let newTab = gBrowser.addTab("data:text/html;charset=utf-8,<h1 id='h1'>" + + aText + "</h1>"); + tabs.push(newTab); + gBrowser.selectedTab = newTab; +} + +function setFindString(aString) { + gFindBar.open(); + gFindBar._findField.focus(); + gFindBar._findField.select(); + EventUtils.sendString(aString); + is(gFindBar._findField.value, aString, "Set the field correctly!"); +} + +var newWindow; + +function test() { + waitForExplicitFinish(); + registerCleanupFunction(function () { + while (tabs.length) { + gBrowser.removeTab(tabs.pop()); + } + }); + texts.forEach(aText => addTabWithText(aText)); + + // Set up the first tab + gBrowser.selectedTab = tabs[0]; + + setFindString(texts[0]); + // Turn on highlight for testing bug 891638 + gFindBar.toggleHighlight(true); + + // Make sure the second tab is correct, then set it up + gBrowser.selectedTab = tabs[1]; + gBrowser.selectedTab.addEventListener("TabFindInitialized", continueTests1); + // Initialize the findbar + gFindBar; +} +function continueTests1() { + gBrowser.selectedTab.removeEventListener("TabFindInitialized", + continueTests1); + ok(true, "'TabFindInitialized' event properly dispatched!"); + ok(gFindBar.hidden, "Second tab doesn't show find bar!"); + gFindBar.open(); + is(gFindBar._findField.value, texts[0], + "Second tab kept old find value for new initialization!"); + setFindString(texts[1]); + + // Confirm the first tab is still correct, ensure re-hiding works as expected + gBrowser.selectedTab = tabs[0]; + ok(!gFindBar.hidden, "First tab shows find bar!"); + // When the Find Clipboard is supported, this test not relevant. + if (!HasFindClipboard) + is(gFindBar._findField.value, texts[0], "First tab persists find value!"); + ok(gFindBar.getElement("highlight").checked, + "Highlight button state persists!"); + + // While we're here, let's test the backout of bug 253793. + gBrowser.reload(); + gBrowser.addEventListener("DOMContentLoaded", continueTests2, true); +} + +function continueTests2() { + gBrowser.removeEventListener("DOMContentLoaded", continueTests2, true); + ok(gFindBar.getElement("highlight").checked, "Highlight never reset!"); + continueTests3(); +} + +function continueTests3() { + ok(gFindBar.getElement("highlight").checked, "Highlight button reset!"); + gFindBar.close(); + ok(gFindBar.hidden, "First tab doesn't show find bar!"); + gBrowser.selectedTab = tabs[1]; + ok(!gFindBar.hidden, "Second tab shows find bar!"); + // Test for bug 892384 + is(gFindBar._findField.getAttribute("focused"), "true", + "Open findbar refocused on tab change!"); + gURLBar.focus(); + gBrowser.selectedTab = tabs[0]; + ok(gFindBar.hidden, "First tab doesn't show find bar!"); + + // Set up a third tab, no tests here + gBrowser.selectedTab = tabs[2]; + setFindString(texts[2]); + + // Now we jump to the second, then first, and then fourth + gBrowser.selectedTab = tabs[1]; + // Test for bug 892384 + ok(!gFindBar._findField.hasAttribute("focused"), + "Open findbar not refocused on tab change!"); + gBrowser.selectedTab = tabs[0]; + gBrowser.selectedTab = tabs[3]; + ok(gFindBar.hidden, "Fourth tab doesn't show find bar!"); + is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!"); + gFindBar.open(); + // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug. + if (!HasFindClipboard) { + is(gFindBar._findField.value, texts[1], + "Fourth tab has second tab's find value!"); + } + + newWindow = gBrowser.replaceTabWithWindow(tabs.pop()); + whenDelayedStartupFinished(newWindow, checkNewWindow); +} + +// Test that findbar gets restored when a tab is moved to a new window. +function checkNewWindow() { + ok(!newWindow.gFindBar.hidden, "New window shows find bar!"); + // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug. + if (!HasFindClipboard) { + is(newWindow.gFindBar._findField.value, texts[1], + "New window find bar has correct find value!"); + } + ok(!newWindow.gFindBar.getElement("find-next").disabled, + "New window findbar has enabled buttons!"); + newWindow.close(); + finish(); +} diff --git a/browser/base/content/test/general/browser_bug537474.js b/browser/base/content/test/general/browser_bug537474.js new file mode 100644 index 000000000..f1139f235 --- /dev/null +++ b/browser/base/content/test/general/browser_bug537474.js @@ -0,0 +1,8 @@ +add_task(function *() { + let browserLoadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + browserDOMWindow.openURI(makeURI("about:"), null, + Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null) + yield browserLoadedPromise; + is(gBrowser.currentURI.spec, "about:", "page loads in the current content window"); +}); + diff --git a/browser/base/content/test/general/browser_bug550565.js b/browser/base/content/test/general/browser_bug550565.js new file mode 100644 index 000000000..b0e094e07 --- /dev/null +++ b/browser/base/content/test/general/browser_bug550565.js @@ -0,0 +1,44 @@ +add_task(function* test() { + let testPath = getRootDirectory(gTestPath); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, + function* (tabBrowser) { + const URI = testPath + "file_with_favicon.html"; + const expectedIcon = testPath + "file_generic_favicon.ico"; + + let got_favicon = Promise.defer(); + let listener = { + onLinkIconAvailable(browser, iconURI) { + if (got_favicon && iconURI && browser === tabBrowser) { + got_favicon.resolve(iconURI); + got_favicon = null; + } + } + }; + gBrowser.addTabsProgressListener(listener); + + BrowserTestUtils.loadURI(tabBrowser, URI); + + let iconURI = yield got_favicon.promise; + is(iconURI, expectedIcon, "Correct icon before pushState."); + + got_favicon = Promise.defer(); + got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e); + yield ContentTask.spawn(tabBrowser, null, function() { + content.history.pushState("page2", "page2", "page2"); + }); + + // We've navigated and shouldn't get a call to onLinkIconAvailable. + TestUtils.executeSoon(() => { + got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser))); + }); + try { + yield got_favicon.promise; + } catch (e) { + iconURI = e; + } + is(iconURI, expectedIcon, "Correct icon after pushState."); + + gBrowser.removeTabsProgressListener(listener); + }); +}); diff --git a/browser/base/content/test/general/browser_bug553455.js b/browser/base/content/test/general/browser_bug553455.js new file mode 100644 index 000000000..c29a810de --- /dev/null +++ b/browser/base/content/test/general/browser_bug553455.js @@ -0,0 +1,1200 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; +const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/"; +const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; +const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; +const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; +const PROGRESS_NOTIFICATION = "addon-progress"; + +const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {}); +const { Task } = Cu.import("resource://gre/modules/Task.jsm"); + +var rootDir = getRootDirectory(gTestPath); +var rootPath = rootDir.split('/'); +var chromeName = rootPath[0] + '//' + rootPath[2]; +var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/"; +var jar = getJar(croot); +if (jar) { + var tmpdir = extractJarToTmp(jar); + croot = 'file://' + tmpdir.path + '/'; +} +const CHROMEROOT = croot; + +var gApp = document.getElementById("bundle_brand").getString("brandShortName"); +var gVersion = Services.appinfo.version; + +function getObserverTopic(aNotificationId) { + let topic = aNotificationId; + if (topic == "xpinstall-disabled") + topic = "addon-install-disabled"; + else if (topic == "addon-progress") + topic = "addon-install-started"; + else if (topic == "addon-install-restart") + topic = "addon-install-complete"; + return topic; +} + +function waitForProgressNotification(aPanelOpen = false, aExpectedCount = 1) { + return Task.spawn(function* () { + let notificationId = PROGRESS_NOTIFICATION; + info("Waiting for " + notificationId + " notification"); + + let topic = getObserverTopic(notificationId); + + let observerPromise = new Promise(resolve => { + Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + // Ignore the progress notification unless that is the notification we want + if (notificationId != PROGRESS_NOTIFICATION && + aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) { + return; + } + Services.obs.removeObserver(observer, topic); + resolve(); + }, topic, false); + }); + + let panelEventPromise; + if (aPanelOpen) { + panelEventPromise = Promise.resolve(); + } else { + panelEventPromise = new Promise(resolve => { + PopupNotifications.panel.addEventListener("popupshowing", function eventListener() { + PopupNotifications.panel.removeEventListener("popupshowing", eventListener); + resolve(); + }); + }); + } + + yield observerPromise; + yield panelEventPromise; + + info("Saw a notification"); + ok(PopupNotifications.isPanelOpen, "Panel should be open"); + is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications"); + if (PopupNotifications.panel.childNodes.length) { + let nodes = Array.from(PopupNotifications.panel.childNodes); + let notification = nodes.find(n => n.id == notificationId + "-notification"); + ok(notification, `Should have seen the right notification`); + } + + return PopupNotifications.panel; + }); +} + +function waitForNotification(aId, aExpectedCount = 1) { + return Task.spawn(function* () { + info("Waiting for " + aId + " notification"); + + let topic = getObserverTopic(aId); + + let observerPromise = new Promise(resolve => { + Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + // Ignore the progress notification unless that is the notification we want + if (aId != PROGRESS_NOTIFICATION && + aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) { + return; + } + Services.obs.removeObserver(observer, topic); + resolve(); + }, topic, false); + }); + + let panelEventPromise = new Promise(resolve => { + PopupNotifications.panel.addEventListener("PanelUpdated", function eventListener(e) { + // Skip notifications that are not the one that we are supposed to be looking for + if (e.detail.indexOf(aId) == -1) { + return; + } + PopupNotifications.panel.removeEventListener("PanelUpdated", eventListener); + resolve(); + }); + }); + + yield observerPromise; + yield panelEventPromise; + + info("Saw a notification"); + ok(PopupNotifications.isPanelOpen, "Panel should be open"); + is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications"); + if (PopupNotifications.panel.childNodes.length) { + let nodes = Array.from(PopupNotifications.panel.childNodes); + let notification = nodes.find(n => n.id == aId + "-notification"); + ok(notification, `Should have seen the right notification`); + } + + return PopupNotifications.panel; + }); +} + +function waitForNotificationClose() { + return new Promise(resolve => { + info("Waiting for notification to close"); + PopupNotifications.panel.addEventListener("popuphidden", function listener() { + PopupNotifications.panel.removeEventListener("popuphidden", listener, false); + resolve(); + }, false); + }); +} + +function waitForInstallDialog() { + return Task.spawn(function* () { + if (Preferences.get("xpinstall.customConfirmationUI", false)) { + yield waitForNotification("addon-install-confirmation"); + return; + } + + info("Waiting for install dialog"); + + let window = yield new Promise(resolve => { + Services.wm.addListener({ + onOpenWindow: function(aXULWindow) { + Services.wm.removeListener(this); + resolve(aXULWindow); + }, + onCloseWindow: function(aXULWindow) { + }, + onWindowTitleChange: function(aXULWindow, aNewTitle) { + } + }); + }); + info("Install dialog opened, waiting for focus"); + + let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + yield new Promise(resolve => { + waitForFocus(function() { + resolve(); + }, domwindow); + }); + info("Saw install dialog"); + is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open"); + + // Override the countdown timer on the accept button + let button = domwindow.document.documentElement.getButton("accept"); + button.disabled = false; + + return; + }); +} + +function removeTab() { + return Promise.all([ + waitForNotificationClose(), + BrowserTestUtils.removeTab(gBrowser.selectedTab) + ]); +} + +function acceptInstallDialog() { + if (Preferences.get("xpinstall.customConfirmationUI", false)) { + document.getElementById("addon-install-confirmation-accept").click(); + } else { + let win = Services.wm.getMostRecentWindow("Addons:Install"); + win.document.documentElement.acceptDialog(); + } +} + +function cancelInstallDialog() { + if (Preferences.get("xpinstall.customConfirmationUI", false)) { + document.getElementById("addon-install-confirmation-cancel").click(); + } else { + let win = Services.wm.getMostRecentWindow("Addons:Install"); + win.document.documentElement.cancelDialog(); + } +} + +function waitForSingleNotification(aCallback) { + return Task.spawn(function* () { + while (PopupNotifications.panel.childNodes.length == 2) { + yield new Promise(resolve => executeSoon(resolve)); + + info("Waiting for single notification"); + // Notification should never close while we wait + ok(PopupNotifications.isPanelOpen, "Notification should still be open"); + } + }); +} + +function setupRedirect(aSettings) { + var url = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs?mode=setup"; + for (var name in aSettings) { + url += "&" + name + "=" + aSettings[name]; + } + + var req = new XMLHttpRequest(); + req.open("GET", url, false); + req.send(null); +} + +function getInstalls() { + return new Promise(resolve => { + AddonManager.getAllInstalls(installs => resolve(installs)); + }); +} + +var TESTS = [ +function test_disabledInstall() { + return Task.spawn(function* () { + Services.prefs.setBoolPref("xpinstall.enabled", false); + + let notificationPromise = waitForNotification("xpinstall-disabled"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Enable", "Should have seen the right button"); + is(notification.getAttribute("label"), + "Software installation is currently disabled. Click Enable and try again."); + + let closePromise = waitForNotificationClose(); + // Click on Enable + EventUtils.synthesizeMouseAtCenter(notification.button, {}); + yield closePromise; + + try { + ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled"); + } + catch (e) { + ok(false, "xpinstall.enabled should be set"); + } + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + let installs = yield getInstalls(); + is(installs.length, 0, "Shouldn't be any pending installs"); + }); +}, + +function test_blockedInstall() { + return Task.spawn(function* () { + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Allow", "Should have seen the right button"); + is(notification.getAttribute("origin"), "example.com", + "Should have seen the right origin host"); + is(notification.getAttribute("label"), + gApp + " prevented this site from asking you to install software on your computer.", + "Should have seen the right message"); + + let dialogPromise = waitForInstallDialog(); + // Click on Allow + EventUtils.synthesizeMouse(notification.button, 20, 10, {}); + // Notification should have changed to progress notification + ok(PopupNotifications.isPanelOpen, "Notification should still be open"); + notification = panel.childNodes[0]; + is(notification.id, "addon-progress-notification", "Should have seen the progress notification"); + yield dialogPromise; + + notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + panel = yield notificationPromise; + + notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "XPI Test will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + installs[0].cancel(); + yield removeTab(); + }); +}, + +function test_whitelistedInstall() { + return Task.spawn(function* () { + let originalTab = gBrowser.selectedTab; + let tab; + gBrowser.selectedTab = originalTab; + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + + triggers).then(newTab => tab = newTab); + yield progressPromise; + yield dialogPromise; + yield BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present"); + + is(gBrowser.selectedTab, tab, + "tab selected in response to the addon-install-confirmation notification"); + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "XPI Test will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + installs[0].cancel(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_failedDownload() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let failPromise = waitForNotification("addon-install-failed"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "missing.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + let panel = yield failPromise; + + let notification = panel.childNodes[0]; + is(notification.getAttribute("label"), + "The add-on could not be downloaded because of a connection failure.", + "Should have seen the right message"); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_corruptFile() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let failPromise = waitForNotification("addon-install-failed"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "corrupt.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + let panel = yield failPromise; + + let notification = panel.childNodes[0]; + is(notification.getAttribute("label"), + "The add-on downloaded from this site could not be installed " + + "because it appears to be corrupt.", + "Should have seen the right message"); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_incompatible() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let failPromise = waitForNotification("addon-install-failed"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "incompatible.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + let panel = yield failPromise; + + let notification = panel.childNodes[0]; + is(notification.getAttribute("label"), + "XPI Test could not be installed because it is not compatible with " + + gApp + " " + gVersion + ".", + "Should have seen the right message"); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_restartless() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "restartless.xpi" + })); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + let notificationPromise = waitForNotification("addon-install-complete"); + acceptInstallDialog(); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.getAttribute("label"), + "XPI Test has been installed successfully.", + "Should have seen the right message"); + + let installs = yield getInstalls(); + is(installs.length, 0, "Should be no pending installs"); + + let addon = yield new Promise(resolve => { + AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", result => { + resolve(result); + }); + }); + addon.uninstall(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + + let closePromise = waitForNotificationClose(); + gBrowser.removeTab(gBrowser.selectedTab); + yield closePromise; + }); +}, + +function test_multiple() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Unsigned XPI": "amosigned.xpi", + "Restartless XPI": "restartless.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + let panel = yield progressPromise; + yield dialogPromise; + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "2 add-ons will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + installs[0].cancel(); + + let addon = yield new Promise(resolve => { + AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function (result) { + resolve(result); + }); + }); + addon.uninstall(); + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_sequential() { + return Task.spawn(function* () { + // This test is only relevant if using the new doorhanger UI + if (!Preferences.get("xpinstall.customConfirmationUI", false)) { + return; + } + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Restartless XPI": "restartless.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + // Should see the right add-on + let container = document.getElementById("addon-install-confirmation-content"); + is(container.childNodes.length, 1, "Should be one item listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + + progressPromise = waitForProgressNotification(true, 2); + triggers = encodeURIComponent(JSON.stringify({ + "Theme XPI": "theme.xpi" + })); + gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + + // Should still have the right add-on in the confirmation notification + is(container.childNodes.length, 1, "Should be one item listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + + // Wait for the install to complete, we won't see a new confirmation + // notification + yield new Promise(resolve => { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver(observer, "addon-install-confirmation"); + resolve(); + }, "addon-install-confirmation", false); + }); + + // Make sure browser-addons.js executes first + yield new Promise(resolve => executeSoon(resolve)); + + // Should have dropped the progress notification + is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications"); + is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification", + "Should only be showing one install confirmation"); + + // Should still have the right add-on in the confirmation notification + is(container.childNodes.length, 1, "Should be one item listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + + cancelInstallDialog(); + + ok(PopupNotifications.isPanelOpen, "Panel should still be open"); + is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications"); + is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification", + "Should still have an install confirmation open"); + + // Should have the next add-on's confirmation dialog + is(container.childNodes.length, 1, "Should be one item listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on"); + + Services.perms.remove(makeURI("http://example.com"), "install"); + let closePromise = waitForNotificationClose(); + cancelInstallDialog(); + yield closePromise; + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + }); +}, + +function test_someUnverified() { + return Task.spawn(function* () { + // This test is only relevant if using the new doorhanger UI and allowing + // unsigned add-ons + if (!Preferences.get("xpinstall.customConfirmationUI", false) || + Preferences.get("xpinstall.signatures.required", true) || + REQUIRE_SIGNING) { + return; + } + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Extension XPI": "restartless-unsigned.xpi", + "Theme XPI": "theme.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + let notification = document.getElementById("addon-install-confirmation-notification"); + let message = notification.getAttribute("label"); + is(message, "Caution: This site would like to install 2 add-ons in " + gApp + + ", some of which are unverified. Proceed at your own risk.", + "Should see the right message"); + + let container = document.getElementById("addon-install-confirmation-content"); + is(container.childNodes.length, 2, "Should be two items listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + is(container.childNodes[0].lastChild.getAttribute("class"), + "addon-install-confirmation-unsigned", "Should have the unverified marker"); + is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on"); + is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker"); + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + yield notificationPromise; + + let [addon, theme] = yield new Promise(resolve => { + AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org", + "theme-xpi@tests.mozilla.org"], + function(addons) { + resolve(addons); + }); + }); + addon.uninstall(); + // Installing a new theme tries to switch to it, switch back to the + // default theme. + theme.userDisabled = true; + theme.uninstall(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_allUnverified() { + return Task.spawn(function* () { + // This test is only relevant if using the new doorhanger UI and allowing + // unsigned add-ons + if (!Preferences.get("xpinstall.customConfirmationUI", false) || + Preferences.get("xpinstall.signatures.required", true) || + REQUIRE_SIGNING) { + return; + } + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Extension XPI": "restartless-unsigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + let notification = document.getElementById("addon-install-confirmation-notification"); + let message = notification.getAttribute("label"); + is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk."); + + let container = document.getElementById("addon-install-confirmation-content"); + is(container.childNodes.length, 1, "Should be one item listed"); + is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on"); + is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker"); + + let notificationPromise = waitForNotification("addon-install-complete"); + acceptInstallDialog(); + yield notificationPromise; + + let addon = yield new Promise(resolve => { + AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(result) { + resolve(result); + }); + }); + addon.uninstall(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_url() { + return Task.spawn(function* () { + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(TESTROOT + "amosigned.xpi"); + yield progressPromise; + yield dialogPromise; + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "XPI Test will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + installs[0].cancel(); + + yield removeTab(); + }); +}, + +function test_localFile() { + return Task.spawn(function* () { + let cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] + .getService(Components.interfaces.nsIChromeRegistry); + let path; + try { + path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec; + } catch (ex) { + path = CHROMEROOT + "corrupt.xpi"; + } + + let failPromise = new Promise(resolve => { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver(observer, "addon-install-failed"); + resolve(); + }, "addon-install-failed", false); + }); + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(path); + yield failPromise; + + // Wait for the browser code to add the failure notification + yield waitForSingleNotification(); + + let notification = PopupNotifications.panel.childNodes[0]; + is(notification.id, "addon-install-failed-notification", "Should have seen the install fail"); + is(notification.getAttribute("label"), + "This add-on could not be installed because it appears to be corrupt.", + "Should have seen the right message"); + + yield removeTab(); + }); +}, + +function test_tabClose() { + return Task.spawn(function* () { + if (!Preferences.get("xpinstall.customConfirmationUI", false)) { + runNextTest(); + return; + } + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(TESTROOT + "amosigned.xpi"); + yield progressPromise; + yield dialogPromise; + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + + let closePromise = waitForNotificationClose(); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + yield closePromise; + + installs = yield getInstalls(); + is(installs.length, 0, "Should be no pending install since the tab is closed"); + }); +}, + +// Add-ons should be cancelled and the install notification destroyed when +// navigating to a new origin +function test_tabNavigate() { + return Task.spawn(function* () { + if (!Preferences.get("xpinstall.customConfirmationUI", false)) { + return; + } + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Extension XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + let closePromise = waitForNotificationClose(); + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI("about:blank"); + yield closePromise; + + let installs = yield getInstalls(); + is(installs.length, 0, "Should be no pending install"); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield loadPromise; + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + }); +}, + +function test_urlBar() { + return Task.spawn(function* () { + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gURLBar.value = TESTROOT + "amosigned.xpi"; + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + + yield progressPromise; + let installDialog = yield dialogPromise; + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(installDialog); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "XPI Test will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + installs[0].cancel(); + + yield removeTab(); + }); +}, + +function test_wrongHost() { + return Task.spawn(function* () { + let requestedUrl = TESTROOT2 + "enabled.html"; + gBrowser.selectedTab = gBrowser.addTab(); + + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl); + gBrowser.loadURI(TESTROOT2 + "enabled.html"); + yield loadedPromise; + + let progressPromise = waitForProgressNotification(); + let notificationPromise = waitForNotification("addon-install-failed"); + gBrowser.loadURI(TESTROOT + "corrupt.xpi"); + yield progressPromise; + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.getAttribute("label"), + "The add-on downloaded from this site could not be installed " + + "because it appears to be corrupt.", + "Should have seen the right message"); + + yield removeTab(); + }); +}, + +function test_reload() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Unsigned XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "XPI Test will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + function testFail() { + ok(false, "Reloading should not have hidden the notification"); + } + PopupNotifications.panel.addEventListener("popuphiding", testFail, false); + let requestedUrl = TESTROOT2 + "enabled.html"; + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl); + gBrowser.loadURI(TESTROOT2 + "enabled.html"); + yield loadedPromise; + PopupNotifications.panel.removeEventListener("popuphiding", testFail, false); + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending install"); + installs[0].cancel(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_theme() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "Theme XPI": "theme.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + is(notification.getAttribute("label"), + "Theme Test will be installed after you restart " + gApp + ".", + "Should have seen the right message"); + + let addon = yield new Promise(resolve => { + AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(result) { + resolve(result); + }); + }); + ok(addon.userDisabled, "Should be switching away from the default theme."); + // Undo the pending theme switch + addon.userDisabled = false; + + addon = yield new Promise(resolve => { + AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(result) { + resolve(result); + }); + }); + isnot(addon, null, "Test theme will have been installed"); + addon.uninstall(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_renotifyBlocked() { + return Task.spawn(function* () { + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + let panel = yield notificationPromise; + + let closePromise = waitForNotificationClose(); + // hide the panel (this simulates the user dismissing it) + panel.hidePopup(); + yield closePromise; + + info("Timeouts after this probably mean bug 589954 regressed"); + + yield new Promise(resolve => executeSoon(resolve)); + + notificationPromise = waitForNotification("addon-install-blocked"); + gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); + yield notificationPromise; + + let installs = yield getInstalls(); + is(installs.length, 2, "Should be two pending installs"); + + closePromise = waitForNotificationClose(); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + yield closePromise; + + installs = yield getInstalls(); + is(installs.length, 0, "Should have cancelled the installs"); + }); +}, + +function test_renotifyInstalled() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let progressPromise = waitForProgressNotification(); + let dialogPromise = waitForInstallDialog(); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + // Wait for the complete notification + let notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + let panel = yield notificationPromise; + + let closePromise = waitForNotificationClose(); + // hide the panel (this simulates the user dismissing it) + panel.hidePopup(); + yield closePromise; + + // Install another + yield new Promise(resolve => executeSoon(resolve)); + + progressPromise = waitForProgressNotification(); + dialogPromise = waitForInstallDialog(); + gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers); + yield progressPromise; + yield dialogPromise; + + info("Timeouts after this probably mean bug 589954 regressed"); + + // Wait for the complete notification + notificationPromise = waitForNotification("addon-install-restart"); + acceptInstallDialog(); + yield notificationPromise; + + let installs = yield getInstalls(); + is(installs.length, 1, "Should be one pending installs"); + installs[0].cancel(); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield removeTab(); + }); +}, + +function test_cancel() { + return Task.spawn(function* () { + let pm = Services.perms; + pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION); + + let notificationPromise = waitForNotification(PROGRESS_NOTIFICATION); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "slowinstall.sjs?file=amosigned.xpi" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + // Close the notification + let anchor = document.getElementById("addons-notification-icon"); + anchor.click(); + // Reopen the notification + anchor.click(); + + ok(PopupNotifications.isPanelOpen, "Notification should still be open"); + is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification"); + notification = panel.childNodes[0]; + is(notification.id, "addon-progress-notification", "Should have seen the progress notification"); + let button = document.getElementById("addon-progress-cancel"); + + // Cancel the download + let install = notification.notification.options.installs[0]; + let cancelledPromise = new Promise(resolve => { + install.addListener({ + onDownloadCancelled: function() { + install.removeListener(this); + resolve(); + } + }); + }); + EventUtils.synthesizeMouseAtCenter(button, {}); + yield cancelledPromise; + + yield new Promise(resolve => executeSoon(resolve)); + + ok(!PopupNotifications.isPanelOpen, "Notification should be closed"); + + let installs = yield getInstalls(); + is(installs.length, 0, "Should be no pending install"); + + Services.perms.remove(makeURI("http://example.com/"), "install"); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + }); +}, + +function test_failedSecurity() { + return Task.spawn(function* () { + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false); + setupRedirect({ + "Location": TESTROOT + "amosigned.xpi" + }); + + let notificationPromise = waitForNotification("addon-install-blocked"); + let triggers = encodeURIComponent(JSON.stringify({ + "XPI": "redirect.sjs?mode=redirect" + })); + BrowserTestUtils.openNewForegroundTab(gBrowser, SECUREROOT + "installtrigger.html?" + triggers); + let panel = yield notificationPromise; + + let notification = panel.childNodes[0]; + // Click on Allow + EventUtils.synthesizeMouse(notification.button, 20, 10, {}); + + // Notification should have changed to progress notification + ok(PopupNotifications.isPanelOpen, "Notification should still be open"); + is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification"); + notification = panel.childNodes[0]; + is(notification.id, "addon-progress-notification", "Should have seen the progress notification"); + + // Wait for it to fail + yield new Promise(resolve => { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver(observer, "addon-install-failed"); + resolve(); + }, "addon-install-failed", false); + }); + + // Allow the browser code to add the failure notification and then wait + // for the progress notification to dismiss itself + yield waitForSingleNotification(); + is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification"); + notification = panel.childNodes[0]; + is(notification.id, "addon-install-failed-notification", "Should have seen the install fail"); + + Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true); + yield removeTab(); + }); +} +]; + +var gTestStart = null; + +var XPInstallObserver = { + observe: function (aSubject, aTopic, aData) { + var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo); + info("Observed " + aTopic + " for " + installInfo.installs.length + " installs"); + installInfo.installs.forEach(function(aInstall) { + info("Install of " + aInstall.sourceURI.spec + " was in state " + aInstall.state); + }); + } +}; + +add_task(function* () { + requestLongerTimeout(4); + + Services.prefs.setBoolPref("extensions.logging.enabled", true); + Services.prefs.setBoolPref("extensions.strictCompatibility", true); + Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false); + Services.prefs.setIntPref("security.dialog_enable_delay", 0); + + Services.obs.addObserver(XPInstallObserver, "addon-install-started", false); + Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false); + Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false); + Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false); + + registerCleanupFunction(function() { + // Make sure no more test parts run in case we were timed out + TESTS = []; + + AddonManager.getAllInstalls(function(aInstalls) { + aInstalls.forEach(function(aInstall) { + aInstall.cancel(); + }); + }); + + Services.prefs.clearUserPref("extensions.logging.enabled"); + Services.prefs.clearUserPref("extensions.strictCompatibility"); + Services.prefs.clearUserPref("extensions.install.requireSecureOrigin"); + Services.prefs.clearUserPref("security.dialog_enable_delay"); + + Services.obs.removeObserver(XPInstallObserver, "addon-install-started"); + Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked"); + Services.obs.removeObserver(XPInstallObserver, "addon-install-failed"); + Services.obs.removeObserver(XPInstallObserver, "addon-install-complete"); + }); + + for (let i = 0; i < TESTS.length; ++i) { + if (gTestStart) + info("Test part took " + (Date.now() - gTestStart) + "ms"); + + ok(!PopupNotifications.isPanelOpen, "Notification should be closed"); + + let installs = yield new Promise(resolve => { + AddonManager.getAllInstalls(function(aInstalls) { + resolve(aInstalls); + }); + }); + + is(installs.length, 0, "Should be no active installs"); + info("Running " + TESTS[i].name); + gTestStart = Date.now(); + yield TESTS[i](); + } +}); diff --git a/browser/base/content/test/general/browser_bug555224.js b/browser/base/content/test/general/browser_bug555224.js new file mode 100644 index 000000000..d27bf0040 --- /dev/null +++ b/browser/base/content/test/general/browser_bug555224.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +const TEST_PAGE = "/browser/browser/base/content/test/general/dummy_page.html"; +var gTestTab, gBgTab, gTestZoom; + +function testBackgroundLoad() { + Task.spawn(function* () { + is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom"); + + yield FullZoomHelper.removeTabAndWaitForLocationChange(gBgTab); + + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTestTab); + finish(); + }); +} + +function testInitialZoom() { + Task.spawn(function* () { + is(ZoomManager.zoom, 1, "initial zoom level should be 1"); + FullZoom.enlarge(); + + gTestZoom = ZoomManager.zoom; + isnot(gTestZoom, 1, "zoom level should have changed"); + + gBgTab = gBrowser.addTab(); + yield FullZoomHelper.load(gBgTab, "http://mochi.test:8888" + TEST_PAGE); + }).then(testBackgroundLoad, FullZoomHelper.failAndContinue(finish)); +} + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + gTestTab = gBrowser.addTab(); + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTestTab); + yield FullZoomHelper.load(gTestTab, "http://example.org" + TEST_PAGE); + }).then(testInitialZoom, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug555767.js b/browser/base/content/test/general/browser_bug555767.js new file mode 100644 index 000000000..bc774f7dc --- /dev/null +++ b/browser/base/content/test/general/browser_bug555767.js @@ -0,0 +1,54 @@ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + add_task(function* () { + let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + let tabSelected = false; + + // Open the base tab + let baseTab = gBrowser.addTab(testURL); + + // Wait for the tab to be fully loaded so matching happens correctly + yield promiseTabLoaded(baseTab); + if (baseTab.linkedBrowser.currentURI.spec == "about:blank") + return; + baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true); + + let testTab = gBrowser.addTab(); + + // Select the testTab + gBrowser.selectedTab = testTab; + + // Set the urlbar to include the moz-action + gURLBar.value = "moz-action:switchtab," + JSON.stringify({url: testURL}); + // Focus the urlbar so we can press enter + gURLBar.focus(); + + // Functions for TabClose and TabSelect + function onTabClose(aEvent) { + gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false); + // Make sure we get the TabClose event for testTab + is(aEvent.originalTarget, testTab, "Got the TabClose event for the right tab"); + // Confirm that we did select the tab + ok(tabSelected, "Confirming that the tab was selected"); + gBrowser.removeTab(baseTab); + finish(); + } + function onTabSelect(aEvent) { + gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false); + // Make sure we got the TabSelect event for baseTab + is(aEvent.originalTarget, baseTab, "Got the TabSelect event for the right tab"); + // Confirm that the selected tab is in fact base tab + is(gBrowser.selectedTab, baseTab, "We've switched to the correct tab"); + tabSelected = true; + } + + // Add the TabClose, TabSelect event listeners before we press enter + gBrowser.tabContainer.addEventListener("TabClose", onTabClose, false); + gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false); + + // Press enter! + EventUtils.synthesizeKey("VK_RETURN", {}); + }); + diff --git a/browser/base/content/test/general/browser_bug559991.js b/browser/base/content/test/general/browser_bug559991.js new file mode 100644 index 000000000..b1516a8b4 --- /dev/null +++ b/browser/base/content/test/general/browser_bug559991.js @@ -0,0 +1,42 @@ +var tab; + +function test() { + + // ---------- + // Test setup + + waitForExplicitFinish(); + + gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true); + gPrefService.setBoolPref("browser.zoom.siteSpecific", true); + + let uri = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + + Task.spawn(function* () { + tab = gBrowser.addTab(); + yield FullZoomHelper.load(tab, uri); + + // ------------------------------------------------------------------- + // Test - Trigger a tab switch that should update the zoom level + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab); + ok(true, "applyPrefToSetting was called"); + }).then(endTest, FullZoomHelper.failAndContinue(endTest)); +} + +// ------------- +// Test clean-up +function endTest() { + Task.spawn(function* () { + yield FullZoomHelper.removeTabAndWaitForLocationChange(tab); + + tab = null; + + if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs")) + gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs"); + + if (gPrefService.prefHasUserValue("browser.zoom.siteSpecific")) + gPrefService.clearUserPref("browser.zoom.siteSpecific"); + + finish(); + }); +} diff --git a/browser/base/content/test/general/browser_bug561636.js b/browser/base/content/test/general/browser_bug561636.js new file mode 100644 index 000000000..69bc475c3 --- /dev/null +++ b/browser/base/content/test/general/browser_bug561636.js @@ -0,0 +1,370 @@ +var gInvalidFormPopup = document.getElementById('invalid-form-popup'); +ok(gInvalidFormPopup, + "The browser should have a popup to show when a form is invalid"); + +function checkPopupShow() +{ + ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open', + "[Test " + testId + "] The invalid form popup should be shown"); +} + +function checkPopupHide() +{ + ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open', + "[Test " + testId + "] The invalid form popup should not be shown"); +} + +var gObserver = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]), + + notifyInvalidSubmit : function (aFormElement, aInvalidElements) + { + } +}; + +var testId = 0; + +function incrementTest() +{ + testId++; + info("Starting next part of test"); +} + +function getDocHeader() +{ + return "<html><head><meta charset='utf-8'></head><body>" + getEmptyFrame(); +} + +function getDocFooter() +{ + return "</body></html>"; +} + +function getEmptyFrame() +{ + return "<iframe style='width:100px; height:30px; margin:3px; border:1px solid lightgray;' " + + "name='t' srcdoc=\"<html><head><meta charset='utf-8'></head><body>form target</body></html>\"></iframe>"; +} + +function* openNewTab(uri, background) +{ + let tab = gBrowser.addTab(); + let browser = gBrowser.getBrowserForTab(tab); + if (!background) { + gBrowser.selectedTab = tab; + } + yield promiseTabLoadEvent(tab, "data:text/html," + escape(uri)); + return browser; +} + +function* clickChildElement(browser) +{ + yield ContentTask.spawn(browser, {}, function* () { + content.document.getElementById('s').click(); + }); +} + +function* blurChildElement(browser) +{ + yield ContentTask.spawn(browser, {}, function* () { + content.document.getElementById('i').blur(); + }); +} + +function* checkChildFocus(browser, message) +{ + yield ContentTask.spawn(browser, [message, testId], function* (args) { + let [msg, id] = args; + var focused = content.document.activeElement == content.document.getElementById('i'); + + var validMsg = true; + if (msg) { + validMsg = (msg == content.document.getElementById('i').validationMessage); + } + + Assert.equal(focused, true, "Test " + id + " First invalid element should be focused"); + Assert.equal(validMsg, true, "Test " + id + " The panel should show the message from validationMessage"); + }); +} + +/** + * In this test, we check that no popup appears if the form is valid. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + yield clickChildElement(browser); + + yield new Promise((resolve, reject) => { + // XXXndeakin This isn't really going to work when the content is another process + executeSoon(function() { + checkPopupHide(); + resolve(); + }); + }); + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that, when an invalid form is submitted, + * the invalid element is focused and a popup appears. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that, when an invalid form is submitted, + * the first invalid element is focused and a popup appears. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input><input id='i' required><input required><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that, we hide the popup by interacting with the + * invalid element if the element becomes valid. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden"); + EventUtils.synthesizeKey("a", {}); + yield popupHiddenPromise; + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that, we don't hide the popup by interacting with the + * invalid element if the element is still invalid. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input type='email' id='i' required><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + yield new Promise((resolve, reject) => { + EventUtils.synthesizeKey("a", {}); + executeSoon(function() { + checkPopupShow(); + resolve(); + }) + }); + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that we can hide the popup by blurring the invalid + * element. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden"); + yield blurChildElement(browser); + yield popupHiddenPromise; + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that we can hide the popup by pressing TAB. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden"); + EventUtils.synthesizeKey("VK_TAB", {}); + yield popupHiddenPromise; + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that the popup will hide if we move to another tab. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter(); + let browser1 = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser1); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser1, gInvalidFormPopup.firstChild.textContent); + + let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden"); + + let browser2 = yield openNewTab("data:text/html,<html></html>"); + yield popupHiddenPromise; + + gBrowser.removeTab(gBrowser.getTabForBrowser(browser1)); + gBrowser.removeTab(gBrowser.getTabForBrowser(browser2)); +}); + +/** + * In this test, we check that nothing happen if the invalid form is + * submitted in a background tab. + */ +add_task(function* () +{ + // Observers don't propagate currently across processes. We may add support for this in the + // future via the addon compat layer. + if (gMultiProcessBrowser) { + return; + } + + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri, true); + isnot(gBrowser.selectedBrowser, browser, "This tab should have been loaded in background"); + + let notifierPromise = new Promise((resolve, reject) => { + gObserver.notifyInvalidSubmit = function() { + executeSoon(function() { + checkPopupHide(); + + // Clean-up + Services.obs.removeObserver(gObserver, "invalidformsubmit"); + gObserver.notifyInvalidSubmit = function () {}; + resolve(); + }); + }; + + Services.obs.addObserver(gObserver, "invalidformsubmit", false); + + executeSoon(function () { + browser.contentDocument.getElementById('s').click(); + }); + }); + + yield notifierPromise; + + gBrowser.removeTab(gBrowser.getTabForBrowser(browser)); +}); + +/** + * In this test, we check that the author defined error message is shown. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input x-moz-errormessage='foo' required id='i'><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + is(gInvalidFormPopup.firstChild.textContent, "foo", + "The panel should show the author defined error message"); + + gBrowser.removeCurrentTab(); +}); + +/** + * In this test, we check that the message is correctly updated when it changes. + */ +add_task(function* () +{ + incrementTest(); + let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input type='email' required id='i'><input id='s' type='submit'></form>" + getDocFooter(); + let browser = yield openNewTab(uri); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + yield clickChildElement(browser); + yield popupShownPromise; + + checkPopupShow(); + yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + + let inputPromise = promiseWaitForEvent(gBrowser.contentDocument.getElementById('i'), "input"); + EventUtils.synthesizeKey('f', {}); + yield inputPromise; + + // Now, the element suffers from another error, the message should have + // been updated. + yield new Promise((resolve, reject) => { + // XXXndeakin This isn't really going to work when the content is another process + executeSoon(function() { + checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent); + resolve(); + }); + }); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug563588.js b/browser/base/content/test/general/browser_bug563588.js new file mode 100644 index 000000000..a1774fb7e --- /dev/null +++ b/browser/base/content/test/general/browser_bug563588.js @@ -0,0 +1,30 @@ +function press(key, expectedPos) { + var originalSelectedTab = gBrowser.selectedTab; + EventUtils.synthesizeKey("VK_" + key.toUpperCase(), { accelKey: true }); + is(gBrowser.selectedTab, originalSelectedTab, + "accel+" + key + " doesn't change which tab is selected"); + is(gBrowser.tabContainer.selectedIndex, expectedPos, + "accel+" + key + " moves the tab to the expected position"); + is(document.activeElement, gBrowser.selectedTab, + "accel+" + key + " leaves the selected tab focused"); +} + +function test() { + gBrowser.addTab(); + gBrowser.addTab(); + is(gBrowser.tabs.length, 3, "got three tabs"); + is(gBrowser.tabs[0], gBrowser.selectedTab, "first tab is selected"); + + gBrowser.selectedTab.focus(); + is(document.activeElement, gBrowser.selectedTab, "selected tab is focused"); + + press("right", 1); + press("down", 2); + press("left", 1); + press("up", 0); + press("end", 2); + press("home", 0); + + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); +} diff --git a/browser/base/content/test/general/browser_bug565575.js b/browser/base/content/test/general/browser_bug565575.js new file mode 100644 index 000000000..3555a2e7f --- /dev/null +++ b/browser/base/content/test/general/browser_bug565575.js @@ -0,0 +1,14 @@ +add_task(function* () { + gBrowser.selectedBrowser.focus(); + + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => BrowserOpenTab(), false); + ok(gURLBar.focused, "location bar is focused for a new tab"); + + yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]); + ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab"); + + yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]); + ok(gURLBar.focused, "location bar is re-focused when selecting the new tab"); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug567306.js b/browser/base/content/test/general/browser_bug567306.js new file mode 100644 index 000000000..742ff6726 --- /dev/null +++ b/browser/base/content/test/general/browser_bug567306.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var {Ci: interfaces, Cc: classes} = Components; + +var Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard); +var HasFindClipboard = Clipboard.supportsFindClipboard(); + +add_task(function* () { + let newwindow = yield BrowserTestUtils.openNewBrowserWindow(); + + let selectedBrowser = newwindow.gBrowser.selectedBrowser; + yield new Promise((resolve, reject) => { + selectedBrowser.addEventListener("pageshow", function pageshowListener() { + if (selectedBrowser.currentURI.spec == "about:blank") + return; + + selectedBrowser.removeEventListener("pageshow", pageshowListener, true); + ok(true, "pageshow listener called: " + newwindow.content.location); + resolve(); + }, true); + selectedBrowser.loadURI("data:text/html,<h1 id='h1'>Select Me</h1>"); + }); + + yield SimpleTest.promiseFocus(newwindow); + + ok(!newwindow.gFindBarInitialized, "find bar is not yet initialized"); + let findBar = newwindow.gFindBar; + + yield ContentTask.spawn(selectedBrowser, { }, function* () { + let elt = content.document.getElementById("h1"); + let selection = content.getSelection(); + let range = content.document.createRange(); + range.setStart(elt, 0); + range.setEnd(elt, 1); + selection.removeAllRanges(); + selection.addRange(range); + }); + + yield findBar.onFindCommand(); + + // When the OS supports the Find Clipboard (OSX), the find field value is + // persisted across Fx sessions, thus not useful to test. + if (!HasFindClipboard) + is(findBar._findField.value, "Select Me", "Findbar is initialized with selection"); + findBar.close(); + yield promiseWindowClosed(newwindow); +}); + diff --git a/browser/base/content/test/general/browser_bug575561.js b/browser/base/content/test/general/browser_bug575561.js new file mode 100644 index 000000000..b6d17a447 --- /dev/null +++ b/browser/base/content/test/general/browser_bug575561.js @@ -0,0 +1,97 @@ +requestLongerTimeout(2); + +const TEST_URL = "http://example.com/browser/browser/base/content/test/general/app_bug575561.html"; + +add_task(function*() { + SimpleTest.requestCompleteLog(); + + // Pinned: Link to the same domain should not open a new tab + // Tests link to http://example.com/browser/browser/base/content/test/general/dummy_page.html + yield testLink(0, true, false); + // Pinned: Link to a different subdomain should open a new tab + // Tests link to http://test1.example.com/browser/browser/base/content/test/general/dummy_page.html + yield testLink(1, true, true); + + // Pinned: Link to a different domain should open a new tab + // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html + yield testLink(2, true, true); + + // Not Pinned: Link to a different domain should not open a new tab + // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html + yield testLink(2, false, false); + + // Pinned: Targetted link should open a new tab + // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html with target="foo" + yield testLink(3, true, true); + + // Pinned: Link in a subframe should not open a new tab + // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html in subframe + yield testLink(0, true, false, true); + + // Pinned: Link to the same domain (with www prefix) should not open a new tab + // Tests link to http://www.example.com/browser/browser/base/content/test/general/dummy_page.html + yield testLink(4, true, false); + + // Pinned: Link to a data: URI should not open a new tab + // Tests link to data:text/html,<!DOCTYPE html><html><body>Another Page</body></html> + yield testLink(5, true, false); + + // Pinned: Link to an about: URI should not open a new tab + // Tests link to about:logo + yield testLink(function(doc) { + let link = doc.createElement("a"); + link.textContent = "Link to Mozilla"; + link.href = "about:logo"; + doc.body.appendChild(link); + return link; + }, true, false, false, "about:robots"); +}); + +var waitForPageLoad = Task.async(function*(browser, linkLocation) { + yield waitForDocLoadComplete(); + + is(browser.contentDocument.location.href, linkLocation, "Link should not open in a new tab"); +}); + +var waitForTabOpen = Task.async(function*() { + let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true); + ok(true, "Link should open a new tab"); + + yield waitForDocLoadComplete(event.target.linkedBrowser); + yield Promise.resolve(); + + gBrowser.removeCurrentTab(); +}); + +var testLink = Task.async(function*(aLinkIndexOrFunction, pinTab, expectNewTab, testSubFrame, aURL = TEST_URL) { + let appTab = gBrowser.addTab(aURL, {skipAnimation: true}); + if (pinTab) + gBrowser.pinTab(appTab); + gBrowser.selectedTab = appTab; + + yield waitForDocLoadComplete(); + + let browser = appTab.linkedBrowser; + if (testSubFrame) + browser = browser.contentDocument.querySelector("iframe"); + + let link; + if (typeof aLinkIndexOrFunction == "function") { + link = aLinkIndexOrFunction(browser.contentDocument); + } else { + link = browser.contentDocument.querySelectorAll("a")[aLinkIndexOrFunction]; + } + + let promise; + if (expectNewTab) + promise = waitForTabOpen(); + else + promise = waitForPageLoad(browser, link.href); + + info("Clicking " + link.textContent); + link.click(); + + yield promise; + + gBrowser.removeTab(appTab); +}); diff --git a/browser/base/content/test/general/browser_bug575830.js b/browser/base/content/test/general/browser_bug575830.js new file mode 100644 index 000000000..5393c08d7 --- /dev/null +++ b/browser/base/content/test/general/browser_bug575830.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +function test() { + let tab1, tab2; + const TEST_IMAGE = "http://example.org/browser/browser/base/content/test/general/moz.png"; + + waitForExplicitFinish(); + + Task.spawn(function* () { + tab1 = gBrowser.addTab(); + tab2 = gBrowser.addTab(); + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1); + yield FullZoomHelper.load(tab1, TEST_IMAGE); + + is(ZoomManager.zoom, 1, "initial zoom level for first should be 1"); + + FullZoom.enlarge(); + let zoom = ZoomManager.zoom; + isnot(zoom, 1, "zoom level should have changed"); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2); + is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1"); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1); + is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed"); + + yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1); + yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2); + }).then(finish, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug577121.js b/browser/base/content/test/general/browser_bug577121.js new file mode 100644 index 000000000..5ebfdc115 --- /dev/null +++ b/browser/base/content/test/general/browser_bug577121.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + Services.prefs.setBoolPref("browser.tabs.animate", false); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("browser.tabs.animate"); + }); + + // Open 2 other tabs, and pin the second one. Like that, the initial tab + // should get closed. + let testTab1 = gBrowser.addTab(); + let testTab2 = gBrowser.addTab(); + gBrowser.pinTab(testTab2); + + // Now execute "Close other Tabs" on the first manually opened tab (tab1). + // -> tab2 ist pinned, tab1 should remain open and the initial tab should + // get closed. + gBrowser.removeAllTabsBut(testTab1); + + is(gBrowser.tabs.length, 2, "there are two remaining tabs open"); + is(gBrowser.tabs[0], testTab2, "pinned tab2 stayed open"); + is(gBrowser.tabs[1], testTab1, "tab1 stayed open"); + + // Cleanup. Close only one tab because we need an opened tab at the end of + // the test. + gBrowser.removeTab(testTab2); +} diff --git a/browser/base/content/test/general/browser_bug578534.js b/browser/base/content/test/general/browser_bug578534.js new file mode 100644 index 000000000..0d61cca76 --- /dev/null +++ b/browser/base/content/test/general/browser_bug578534.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* test() { + let uriString = "http://example.com/"; + let cookieBehavior = "network.cookie.cookieBehavior"; + let uriObj = Services.io.newURI(uriString, null, null) + let cp = Components.classes["@mozilla.org/cookie/permission;1"] + .getService(Components.interfaces.nsICookiePermission); + + yield SpecialPowers.pushPrefEnv({ set: [[ cookieBehavior, 2 ]] }); + cp.setAccess(uriObj, cp.ACCESS_ALLOW); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: uriString }, function* (browser) { + yield ContentTask.spawn(browser, null, function() { + is(content.navigator.cookieEnabled, true, + "navigator.cookieEnabled should be true"); + }); + }); + + cp.setAccess(uriObj, cp.ACCESS_DEFAULT); +}); diff --git a/browser/base/content/test/general/browser_bug579872.js b/browser/base/content/test/general/browser_bug579872.js new file mode 100644 index 000000000..bc10ca0c8 --- /dev/null +++ b/browser/base/content/test/general/browser_bug579872.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + let newTab = gBrowser.addTab(); + waitForExplicitFinish(); + BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart); + + function mainPart() { + gBrowser.pinTab(newTab); + gBrowser.selectedTab = newTab; + + openUILinkIn("javascript:var x=0;", "current"); + is(gBrowser.tabs.length, 2, "Should open in current tab"); + + openUILinkIn("http://example.com/1", "current"); + is(gBrowser.tabs.length, 2, "Should open in current tab"); + + openUILinkIn("http://example.org/", "current"); + is(gBrowser.tabs.length, 3, "Should open in new tab"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab + finish(); + } + newTab.linkedBrowser.loadURI("http://example.com"); +} diff --git a/browser/base/content/test/general/browser_bug580638.js b/browser/base/content/test/general/browser_bug580638.js new file mode 100644 index 000000000..66defafe3 --- /dev/null +++ b/browser/base/content/test/general/browser_bug580638.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + function testState(aPinned) { + function elemAttr(id, attr) { + return document.getElementById(id).getAttribute(attr); + } + + if (aPinned) { + is(elemAttr("key_close", "disabled"), "true", + "key_close should be disabled when a pinned-tab is selected"); + is(elemAttr("menu_close", "key"), "", + "menu_close shouldn't have a key set when a pinned is selected"); + } + else { + is(elemAttr("key_close", "disabled"), "", + "key_closed shouldn't have disabled state set when a non-pinned tab is selected"); + is(elemAttr("menu_close", "key"), "key_close", + "menu_close should have key_close set as its key when a non-pinned tab is selected"); + } + } + + let lastSelectedTab = gBrowser.selectedTab; + ok(!lastSelectedTab.pinned, "We should have started with a regular tab selected"); + + testState(false); + + let pinnedTab = gBrowser.addTab("about:blank"); + gBrowser.pinTab(pinnedTab); + + // Just pinning the tab shouldn't change the key state. + testState(false); + + // Test updating key state after selecting a tab. + gBrowser.selectedTab = pinnedTab; + testState(true); + + gBrowser.selectedTab = lastSelectedTab; + testState(false); + + gBrowser.selectedTab = pinnedTab; + testState(true); + + // Test updating the key state after un/pinning the tab. + gBrowser.unpinTab(pinnedTab); + testState(false); + + gBrowser.pinTab(pinnedTab); + testState(true); + + // Test updating the key state after removing the tab. + gBrowser.removeTab(pinnedTab); + testState(false); + + finish(); +} diff --git a/browser/base/content/test/general/browser_bug580956.js b/browser/base/content/test/general/browser_bug580956.js new file mode 100644 index 000000000..b8e7bc20b --- /dev/null +++ b/browser/base/content/test/general/browser_bug580956.js @@ -0,0 +1,26 @@ +function numClosedTabs() { + return SessionStore.getClosedTabCount(window); +} + +function isUndoCloseEnabled() { + updateTabContextMenu(); + return !document.getElementById("context_undoCloseTab").disabled; +} + +function test() { + waitForExplicitFinish(); + + gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", 0); + gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo"); + is(numClosedTabs(), 0, "There should be 0 closed tabs."); + ok(!isUndoCloseEnabled(), "Undo Close Tab should be disabled."); + + var tab = gBrowser.addTab("http://mochi.test:8888/"); + var browser = gBrowser.getBrowserForTab(tab); + BrowserTestUtils.browserLoaded(browser).then(() => { + BrowserTestUtils.removeTab(tab).then(() => { + ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled."); + finish(); + }); + }); +} diff --git a/browser/base/content/test/general/browser_bug581242.js b/browser/base/content/test/general/browser_bug581242.js new file mode 100644 index 000000000..668c0cd41 --- /dev/null +++ b/browser/base/content/test/general/browser_bug581242.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + // Create a new tab and load about:addons + let blanktab = gBrowser.addTab(); + gBrowser.selectedTab = blanktab; + BrowserOpenAddonsMgr(); + + is(blanktab, gBrowser.selectedTab, "Current tab should be blank tab"); + // Verify that about:addons loads + waitForExplicitFinish(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + let browser = blanktab.linkedBrowser; + is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab."); + gBrowser.removeTab(blanktab); + finish(); + }, true); +} diff --git a/browser/base/content/test/general/browser_bug581253.js b/browser/base/content/test/general/browser_bug581253.js new file mode 100644 index 000000000..0c537c3d3 --- /dev/null +++ b/browser/base/content/test/general/browser_bug581253.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var testURL = "data:text/plain,nothing but plain text"; +var testTag = "581253_tag"; +var timerID = -1; + +function test() { + registerCleanupFunction(function() { + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + if (timerID > 0) { + clearTimeout(timerID); + } + }); + waitForExplicitFinish(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + tab.linkedBrowser.addEventListener("load", (function(event) { + tab.linkedBrowser.removeEventListener("load", arguments.callee, true); + + let uri = makeURI(testURL); + let bmTxn = + new PlacesCreateBookmarkTransaction(uri, + PlacesUtils.unfiledBookmarksFolderId, + -1, "", null, []); + PlacesUtils.transactionManager.doTransaction(bmTxn); + + ok(PlacesUtils.bookmarks.isBookmarked(uri), "the test url is bookmarked"); + waitForStarChange(true, onStarred); + }), true); + + content.location = testURL; +} + +function waitForStarChange(aValue, aCallback) { + let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED + : BookmarkingUI.STATUS_UNSTARRED; + if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING || + BookmarkingUI.status != expectedStatus) { + info("Waiting for star button change."); + setTimeout(waitForStarChange, 50, aValue, aCallback); + return; + } + aCallback(); +} + +function onStarred() { + is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, + "star button indicates that the page is bookmarked"); + + let uri = makeURI(testURL); + let tagTxn = new PlacesTagURITransaction(uri, [testTag]); + PlacesUtils.transactionManager.doTransaction(tagTxn); + + StarUI.panel.addEventListener("popupshown", onPanelShown, false); + BookmarkingUI.star.click(); +} + +function onPanelShown(aEvent) { + if (aEvent.target == StarUI.panel) { + StarUI.panel.removeEventListener("popupshown", arguments.callee, false); + let tagsField = document.getElementById("editBMPanel_tagsField"); + ok(tagsField.value == testTag, "tags field value was set"); + tagsField.focus(); + + StarUI.panel.addEventListener("popuphidden", onPanelHidden, false); + let removeButton = document.getElementById("editBookmarkPanelRemoveButton"); + removeButton.click(); + } +} + +function onPanelHidden(aEvent) { + if (aEvent.target == StarUI.panel) { + StarUI.panel.removeEventListener("popuphidden", arguments.callee, false); + + executeSoon(function() { + ok(!PlacesUtils.bookmarks.isBookmarked(makeURI(testURL)), + "the bookmark for the test url has been removed"); + is(BookmarkingUI.status, BookmarkingUI.STATUS_UNSTARRED, + "star button indicates that the bookmark has been removed"); + gBrowser.removeCurrentTab(); + PlacesTestUtils.clearHistory().then(finish); + }); + } +} diff --git a/browser/base/content/test/general/browser_bug585558.js b/browser/base/content/test/general/browser_bug585558.js new file mode 100644 index 000000000..bae832b4d --- /dev/null +++ b/browser/base/content/test/general/browser_bug585558.js @@ -0,0 +1,153 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var tabs = []; + +function addTab(aURL) { + tabs.push(gBrowser.addTab(aURL, {skipAnimation: true})); +} + +function testAttrib(elem, attrib, attribValue, msg) { + is(elem.hasAttribute(attrib), attribValue, msg); +} + +function test() { + waitForExplicitFinish(); + + is(gBrowser.tabs.length, 1, "one tab is open initially"); + + // Add several new tabs in sequence, hiding some, to ensure that the + // correct attributes get set + + addTab("http://mochi.test:8888/#0"); + addTab("http://mochi.test:8888/#1"); + addTab("http://mochi.test:8888/#2"); + addTab("http://mochi.test:8888/#3"); + + gBrowser.selectedTab = gBrowser.tabs[0]; + testAttrib(gBrowser.tabs[0], "first-visible-tab", true, + "First tab marked first-visible-tab!"); + testAttrib(gBrowser.tabs[4], "last-visible-tab", true, + "Fifth tab marked last-visible-tab!"); + testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!"); + testAttrib(gBrowser.tabs[0], "afterselected-visible", false, + "First tab not marked afterselected-visible!"); + testAttrib(gBrowser.tabs[1], "afterselected-visible", true, + "Second tab marked afterselected-visible!"); + gBrowser.hideTab(gBrowser.tabs[1]); + executeSoon(test_hideSecond); +} + +function test_hideSecond() { + testAttrib(gBrowser.tabs[2], "afterselected-visible", true, + "Third tab marked afterselected-visible!"); + gBrowser.showTab(gBrowser.tabs[1]) + executeSoon(test_showSecond); +} + +function test_showSecond() { + testAttrib(gBrowser.tabs[1], "afterselected-visible", true, + "Second tab marked afterselected-visible!"); + testAttrib(gBrowser.tabs[2], "afterselected-visible", false, + "Third tab not marked as afterselected-visible!"); + gBrowser.selectedTab = gBrowser.tabs[1]; + gBrowser.hideTab(gBrowser.tabs[0]); + executeSoon(test_hideFirst); +} + +function test_hideFirst() { + testAttrib(gBrowser.tabs[0], "first-visible-tab", false, + "Hidden first tab not marked first-visible-tab!"); + testAttrib(gBrowser.tabs[1], "first-visible-tab", true, + "Second tab marked first-visible-tab!"); + gBrowser.showTab(gBrowser.tabs[0]); + executeSoon(test_showFirst); +} + +function test_showFirst() { + testAttrib(gBrowser.tabs[0], "first-visible-tab", true, + "First tab marked first-visible-tab!"); + gBrowser.selectedTab = gBrowser.tabs[2]; + testAttrib(gBrowser.tabs[3], "afterselected-visible", true, + "Fourth tab marked afterselected-visible!"); + + gBrowser.moveTabTo(gBrowser.selectedTab, 1); + executeSoon(test_movedLower); +} + +function test_movedLower() { + testAttrib(gBrowser.tabs[2], "afterselected-visible", true, + "Third tab marked afterselected-visible!"); + test_hoverOne(); +} + +function test_hoverOne() { + EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[4], { type: "mousemove" }); + testAttrib(gBrowser.tabs[3], "beforehovered", true, "Fourth tab marked beforehovered"); + EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[3], { type: "mousemove" }); + testAttrib(gBrowser.tabs[2], "beforehovered", true, "Third tab marked beforehovered!"); + testAttrib(gBrowser.tabs[2], "afterhovered", false, "Third tab not marked afterhovered!"); + testAttrib(gBrowser.tabs[4], "afterhovered", true, "Fifth tab marked afterhovered!"); + testAttrib(gBrowser.tabs[4], "beforehovered", false, "Fifth tab not marked beforehovered!"); + testAttrib(gBrowser.tabs[0], "beforehovered", false, "First tab not marked beforehovered!"); + testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!"); + testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!"); + testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!"); + testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!"); + testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!"); + gBrowser.removeTab(tabs.pop()); + executeSoon(test_hoverStatePersistence); +} + +function test_hoverStatePersistence() { + // Test that the afterhovered and beforehovered attributes are still there when + // a tab is selected and then unselected again. See bug 856107. + + function assertState() { + testAttrib(gBrowser.tabs[0], "beforehovered", true, "First tab still marked beforehovered!"); + testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!"); + testAttrib(gBrowser.tabs[2], "afterhovered", true, "Third tab still marked afterhovered!"); + testAttrib(gBrowser.tabs[2], "beforehovered", false, "Third tab not marked afterhovered!"); + testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!"); + testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!"); + testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!"); + testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!"); + } + + gBrowser.selectedTab = gBrowser.tabs[3]; + EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[1], { type: "mousemove" }); + assertState(); + gBrowser.selectedTab = gBrowser.tabs[1]; + assertState(); + gBrowser.selectedTab = gBrowser.tabs[3]; + assertState(); + executeSoon(test_pinning); +} + +function test_pinning() { + gBrowser.selectedTab = gBrowser.tabs[3]; + testAttrib(gBrowser.tabs[3], "last-visible-tab", true, + "Fourth tab marked last-visible-tab!"); + testAttrib(gBrowser.tabs[3], "selected", true, "Fourth tab marked selected!"); + testAttrib(gBrowser.tabs[3], "afterselected-visible", false, + "Fourth tab not marked afterselected-visible!"); + // Causes gBrowser.tabs to change indices + gBrowser.pinTab(gBrowser.tabs[3]); + testAttrib(gBrowser.tabs[3], "last-visible-tab", true, + "Fourth tab marked last-visible-tab!"); + testAttrib(gBrowser.tabs[1], "afterselected-visible", true, + "Second tab marked afterselected-visible!"); + testAttrib(gBrowser.tabs[0], "first-visible-tab", true, + "First tab marked first-visible-tab!"); + testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!"); + gBrowser.selectedTab = gBrowser.tabs[1]; + testAttrib(gBrowser.tabs[2], "afterselected-visible", true, + "Third tab marked afterselected-visible!"); + test_cleanUp(); +} + +function test_cleanUp() { + tabs.forEach(gBrowser.removeTab, gBrowser); + finish(); +} diff --git a/browser/base/content/test/general/browser_bug585785.js b/browser/base/content/test/general/browser_bug585785.js new file mode 100644 index 000000000..4f9045231 --- /dev/null +++ b/browser/base/content/test/general/browser_bug585785.js @@ -0,0 +1,35 @@ +var tab; + +function test() { + waitForExplicitFinish(); + + tab = gBrowser.addTab(); + isnot(tab.getAttribute("fadein"), "true", "newly opened tab is yet to fade in"); + + // Try to remove the tab right before the opening animation's first frame + window.requestAnimationFrame(checkAnimationState); +} + +function checkAnimationState() { + is(tab.getAttribute("fadein"), "true", "tab opening animation initiated"); + + info(window.getComputedStyle(tab).maxWidth); + gBrowser.removeTab(tab, { animate: true }); + if (!tab.parentNode) { + ok(true, "tab removed synchronously since the opening animation hasn't moved yet"); + finish(); + return; + } + + info("tab didn't close immediately, so the tab opening animation must have started moving"); + info("waiting for the tab to close asynchronously"); + tab.addEventListener("transitionend", function (event) { + if (event.propertyName == "max-width") { + tab.removeEventListener("transitionend", arguments.callee, false); + executeSoon(function () { + ok(!tab.parentNode, "tab removed asynchronously"); + finish(); + }); + } + }, false); +} diff --git a/browser/base/content/test/general/browser_bug585830.js b/browser/base/content/test/general/browser_bug585830.js new file mode 100644 index 000000000..6d3adf198 --- /dev/null +++ b/browser/base/content/test/general/browser_bug585830.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + let tab1 = gBrowser.selectedTab; + let tab2 = gBrowser.addTab("about:blank", {skipAnimation: true}); + gBrowser.addTab(); + gBrowser.selectedTab = tab2; + + gBrowser.removeCurrentTab({animate: true}); + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(gBrowser.selectedTab, tab1, "First tab should be selected"); + gBrowser.removeTab(tab2); + + // test for "null has no properties" fix. See Bug 585830 Comment 13 + gBrowser.removeCurrentTab({animate: true}); + try { + gBrowser.tabContainer.advanceSelectedTab(-1, false); + } catch (err) { + ok(false, "Shouldn't throw"); + } + + gBrowser.removeTab(tab1); +} diff --git a/browser/base/content/test/general/browser_bug590206.js b/browser/base/content/test/general/browser_bug590206.js new file mode 100644 index 000000000..f73d144e9 --- /dev/null +++ b/browser/base/content/test/general/browser_bug590206.js @@ -0,0 +1,163 @@ +/* + * Test the identity mode UI for a variety of page types + */ + +"use strict"; + +const DUMMY = "browser/browser/base/content/test/general/dummy_page.html"; + +function loadNewTab(url) { + return BrowserTestUtils.openNewForegroundTab(gBrowser, url); +} + +function getIdentityMode() { + return document.getElementById("identity-box").className; +} + +function getConnectionState() { + gIdentityHandler.refreshIdentityPopup(); + return document.getElementById("identity-popup").getAttribute("connection"); +} + +// This test is slow on Linux debug e10s +requestLongerTimeout(2); + +add_task(function* test_webpage() { + let oldTab = gBrowser.selectedTab; + + let newTab = yield loadNewTab("http://example.com/" + DUMMY); + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_blank() { + let oldTab = gBrowser.selectedTab; + + let newTab = yield loadNewTab("about:blank"); + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_chrome() { + let oldTab = gBrowser.selectedTab; + + let newTab = yield loadNewTab("chrome://mozapps/content/extensions/extensions.xul"); + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_https() { + let oldTab = gBrowser.selectedTab; + + let newTab = yield loadNewTab("https://example.com/" + DUMMY); + is(getIdentityMode(), "verifiedDomain", "Identity should be verified"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "verifiedDomain", "Identity should be verified"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_addons() { + let oldTab = gBrowser.selectedTab; + + let newTab = yield loadNewTab("about:addons"); + is(getIdentityMode(), "chromeUI", "Identity should be chrome"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "chromeUI", "Identity should be chrome"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_file() { + let oldTab = gBrowser.selectedTab; + let fileURI = getTestFilePath(""); + + let newTab = yield loadNewTab(fileURI); + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_resource_uri() { + let oldTab = gBrowser.selectedTab; + let dataURI = "resource://gre/modules/Services.jsm"; + + let newTab = yield loadNewTab(dataURI); + + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_data_uri() { + let oldTab = gBrowser.selectedTab; + let dataURI = "data:text/html,hi" + + let newTab = yield loadNewTab(dataURI); + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.removeTab(newTab); +}); + +add_task(function* test_about_uri() { + let oldTab = gBrowser.selectedTab; + let aboutURI = "about:robots" + + let newTab = yield loadNewTab(aboutURI); + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); +}); diff --git a/browser/base/content/test/general/browser_bug592338.js b/browser/base/content/test/general/browser_bug592338.js new file mode 100644 index 000000000..ca9cc361a --- /dev/null +++ b/browser/base/content/test/general/browser_bug592338.js @@ -0,0 +1,163 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/"; + +var tempScope = {}; +Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope); +var LightweightThemeManager = tempScope.LightweightThemeManager; + +function wait_for_notification(aCallback) { + PopupNotifications.panel.addEventListener("popupshown", function() { + PopupNotifications.panel.removeEventListener("popupshown", arguments.callee, false); + aCallback(PopupNotifications.panel); + }, false); +} + +var TESTS = [ +function test_install_http() { + is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected"); + + var pm = Services.perms; + pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION); + + gBrowser.selectedTab = gBrowser.addTab("http://example.org/browser/browser/base/content/test/general/bug592338.html"); + gBrowser.selectedBrowser.addEventListener("pageshow", function() { + if (gBrowser.contentDocument.location.href == "about:blank") + return; + + gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false); + + executeSoon(function() { + BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser); + + is(LightweightThemeManager.currentTheme, null, "Should not have installed the test theme"); + + gBrowser.removeTab(gBrowser.selectedTab); + + pm.remove(makeURI("http://example.org/"), "install"); + + runNextTest(); + }); + }, false); +}, + +function test_install_lwtheme() { + is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected"); + + var pm = Services.perms; + pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION); + + gBrowser.selectedTab = gBrowser.addTab("https://example.com/browser/browser/base/content/test/general/bug592338.html"); + gBrowser.selectedBrowser.addEventListener("pageshow", function() { + if (gBrowser.contentDocument.location.href == "about:blank") + return; + + gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false); + + BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser); + let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser); + waitForCondition( + () => notificationBox.getNotificationWithValue("lwtheme-install-notification"), + () => { + is(LightweightThemeManager.currentTheme.id, "test", "Should have installed the test theme"); + + LightweightThemeManager.currentTheme = null; + gBrowser.removeTab(gBrowser.selectedTab); + Services.perms.remove(makeURI("http://example.com/"), "install"); + + runNextTest(); + } + ); + }, false); +}, + +function test_lwtheme_switch_theme() { + is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected"); + + AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) { + aAddon.userDisabled = false; + ok(aAddon.isActive, "Theme should have immediately enabled"); + Services.prefs.setBoolPref("extensions.dss.enabled", false); + + var pm = Services.perms; + pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION); + + gBrowser.selectedTab = gBrowser.addTab("https://example.com/browser/browser/base/content/test/general/bug592338.html"); + gBrowser.selectedBrowser.addEventListener("pageshow", function() { + if (gBrowser.contentDocument.location.href == "about:blank") + return; + + gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false); + + executeSoon(function() { + wait_for_notification(function(aPanel) { + is(LightweightThemeManager.currentTheme, null, "Should not have installed the test lwtheme"); + ok(aAddon.isActive, "Test theme should still be active"); + + let notification = aPanel.childNodes[0]; + is(notification.button.label, "Restart Now", "Should have seen the right button"); + + ok(aAddon.userDisabled, "Should be waiting to disable the test theme"); + aAddon.userDisabled = false; + Services.prefs.setBoolPref("extensions.dss.enabled", true); + + gBrowser.removeTab(gBrowser.selectedTab); + + Services.perms.remove(makeURI("http://example.com"), "install"); + + runNextTest(); + }); + BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser); + }); + }, false); + }); +} +]; + +function runNextTest() { + AddonManager.getAllInstalls(function(aInstalls) { + is(aInstalls.length, 0, "Should be no active installs"); + + if (TESTS.length == 0) { + AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) { + aAddon.uninstall(); + + Services.prefs.setBoolPref("extensions.logging.enabled", false); + Services.prefs.setBoolPref("extensions.dss.enabled", false); + + finish(); + }); + return; + } + + info("Running " + TESTS[0].name); + TESTS.shift()(); + }); +} + +function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref("extensions.logging.enabled", true); + + AddonManager.getInstallForURL(TESTROOT + "theme.xpi", function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) { + isnot(aAddon, null, "Should have installed the test theme."); + + // In order to switch themes while the test is running we turn on dynamic + // theme switching. This means the test isn't exactly correct but should + // do some good + Services.prefs.setBoolPref("extensions.dss.enabled", true); + + runNextTest(); + }); + } + }); + + aInstall.install(); + }, "application/x-xpinstall"); +} diff --git a/browser/base/content/test/general/browser_bug594131.js b/browser/base/content/test/general/browser_bug594131.js new file mode 100644 index 000000000..ce09026ac --- /dev/null +++ b/browser/base/content/test/general/browser_bug594131.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + let newTab = gBrowser.addTab("http://example.com"); + waitForExplicitFinish(); + BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart); + + function mainPart() { + gBrowser.pinTab(newTab); + gBrowser.selectedTab = newTab; + + openUILinkIn("http://example.org/", "current", { inBackground: true }); + isnot(gBrowser.selectedTab, newTab, "shouldn't load in background"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab + finish(); + } +} diff --git a/browser/base/content/test/general/browser_bug595507.js b/browser/base/content/test/general/browser_bug595507.js new file mode 100644 index 000000000..54ae42346 --- /dev/null +++ b/browser/base/content/test/general/browser_bug595507.js @@ -0,0 +1,36 @@ +/** + * Make sure that the form validation error message shows even if the form is in an iframe. + */ +add_task(function* () { + let uri = "<iframe src=\"data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>\"</iframe>"; + + var gInvalidFormPopup = document.getElementById('invalid-form-popup'); + ok(gInvalidFormPopup, + "The browser should have a popup to show when a form is invalid"); + + let tab = gBrowser.addTab(); + let browser = gBrowser.getBrowserForTab(tab); + gBrowser.selectedTab = tab; + + yield promiseTabLoadEvent(tab, "data:text/html," + escape(uri)); + + let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown"); + + yield ContentTask.spawn(browser, {}, function* () { + content.document.getElementsByTagName('iframe')[0] + .contentDocument.getElementById('s').click(); + }); + yield popupShownPromise; + + yield ContentTask.spawn(browser, {}, function* () { + let childdoc = content.document.getElementsByTagName('iframe')[0].contentDocument; + Assert.equal(childdoc.activeElement, childdoc.getElementById("i"), + "First invalid element should be focused"); + }); + + ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open', + "The invalid form popup should be shown"); + + gBrowser.removeCurrentTab(); +}); + diff --git a/browser/base/content/test/general/browser_bug596687.js b/browser/base/content/test/general/browser_bug596687.js new file mode 100644 index 000000000..5c2b4fbfe --- /dev/null +++ b/browser/base/content/test/general/browser_bug596687.js @@ -0,0 +1,25 @@ +add_task(function* test() { + var tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + var gotTabAttrModified = false; + var gotTabClose = false; + + function onTabClose() { + gotTabClose = true; + tab.addEventListener("TabAttrModified", onTabAttrModified, false); + } + + function onTabAttrModified() { + gotTabAttrModified = true; + } + + tab.addEventListener("TabClose", onTabClose, false); + + yield BrowserTestUtils.removeTab(tab); + + ok(gotTabClose, "should have got the TabClose event"); + ok(!gotTabAttrModified, "shouldn't have got the TabAttrModified event after TabClose"); + + tab.removeEventListener("TabClose", onTabClose, false); + tab.removeEventListener("TabAttrModified", onTabAttrModified, false); +}); diff --git a/browser/base/content/test/general/browser_bug597218.js b/browser/base/content/test/general/browser_bug597218.js new file mode 100644 index 000000000..5f4ededc3 --- /dev/null +++ b/browser/base/content/test/general/browser_bug597218.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + // establish initial state + is(gBrowser.tabs.length, 1, "we start with one tab"); + + // create a tab + let tab = gBrowser.loadOneTab("about:blank"); + ok(!tab.hidden, "tab starts out not hidden"); + is(gBrowser.tabs.length, 2, "we now have two tabs"); + + // make sure .hidden is read-only + tab.hidden = true; + ok(!tab.hidden, "can't set .hidden directly"); + + // hide the tab + gBrowser.hideTab(tab); + ok(tab.hidden, "tab is hidden"); + + // now pin it and make sure it gets unhidden + gBrowser.pinTab(tab); + ok(tab.pinned, "tab was pinned"); + ok(!tab.hidden, "tab was unhidden"); + + // try hiding it now that it's pinned; shouldn't be able to + gBrowser.hideTab(tab); + ok(!tab.hidden, "tab did not hide"); + + // clean up + gBrowser.removeTab(tab); + is(gBrowser.tabs.length, 1, "we finish with one tab"); + + finish(); +} diff --git a/browser/base/content/test/general/browser_bug609700.js b/browser/base/content/test/general/browser_bug609700.js new file mode 100644 index 000000000..8b4f1ea91 --- /dev/null +++ b/browser/base/content/test/general/browser_bug609700.js @@ -0,0 +1,20 @@ +function test() { + waitForExplicitFinish(); + + Services.ww.registerNotification(function (aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + Services.ww.unregisterNotification(arguments.callee); + + ok(true, "duplicateTabIn opened a new window"); + + whenDelayedStartupFinished(aSubject, function () { + executeSoon(function () { + aSubject.close(); + finish(); + }); + }, false); + } + }); + + duplicateTabIn(gBrowser.selectedTab, "window"); +} diff --git a/browser/base/content/test/general/browser_bug623893.js b/browser/base/content/test/general/browser_bug623893.js new file mode 100644 index 000000000..fa6da1b22 --- /dev/null +++ b/browser/base/content/test/general/browser_bug623893.js @@ -0,0 +1,37 @@ +add_task(function* test() { + yield BrowserTestUtils.withNewTab("data:text/plain;charset=utf-8,1", function* (browser) { + BrowserTestUtils.loadURI(browser, "data:text/plain;charset=utf-8,2"); + yield BrowserTestUtils.browserLoaded(browser); + + BrowserTestUtils.loadURI(browser, "data:text/plain;charset=utf-8,3"); + yield BrowserTestUtils.browserLoaded(browser); + + yield duplicate(0, "maintained the original index"); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + + yield duplicate(-1, "went back"); + yield duplicate(1, "went forward"); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + }); +}); + +function promiseGetIndex(browser) { + return ContentTask.spawn(browser, null, function() { + let shistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISHistory); + return shistory.index; + }); +} + +let duplicate = Task.async(function* (delta, msg, cb) { + var startIndex = yield promiseGetIndex(gBrowser.selectedBrowser); + + duplicateTabIn(gBrowser.selectedTab, "tab", delta); + + let tab = gBrowser.selectedTab; + yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored"); + + let endIndex = yield promiseGetIndex(gBrowser.selectedBrowser); + is(endIndex, startIndex + delta, msg); +}); diff --git a/browser/base/content/test/general/browser_bug624734.js b/browser/base/content/test/general/browser_bug624734.js new file mode 100644 index 000000000..d6fc7acbc --- /dev/null +++ b/browser/base/content/test/general/browser_bug624734.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Bug 624734 - Star UI has no tooltip until bookmarked page is visited + +function finishTest() { + is(BookmarkingUI.button.getAttribute("buttontooltiptext"), + BookmarkingUI._unstarredTooltip, + "Star icon should have the unstarred tooltip text"); + + gBrowser.removeCurrentTab(); + finish(); +} + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING) { + waitForCondition(() => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING, finishTest, "BookmarkingUI was updating for too long"); + } else { + finishTest(); + } + }); + + tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/general/dummy_page.html"); +} diff --git a/browser/base/content/test/general/browser_bug633691.js b/browser/base/content/test/general/browser_bug633691.js new file mode 100644 index 000000000..28a8440ff --- /dev/null +++ b/browser/base/content/test/general/browser_bug633691.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(function* test() { + const URL = "data:text/html,<iframe width='700' height='700'></iframe>"; + yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, function* (browser) { + yield ContentTask.spawn(browser, + { is_element_hidden_: is_element_hidden.toSource(), + is_hidden_: is_hidden.toSource() }, + function* ({ is_element_hidden_, is_hidden_ }) { + let loadError = + ContentTaskUtils.waitForEvent(this, "AboutNetErrorLoad", false, null, true); + let iframe = content.document.querySelector("iframe"); + iframe.src = "https://expired.example.com/"; + + yield loadError; + + let is_hidden = eval(`(() => ${is_hidden_})()`); + let is_element_hidden = eval(`(() => ${is_element_hidden_})()`); + let doc = content.document.getElementsByTagName("iframe")[0].contentDocument; + let aP = doc.getElementById("badCertAdvancedPanel"); + ok(aP, "Advanced content should exist"); + void is_hidden; // Quiet eslint warnings (actual use under is_element_hidden) + is_element_hidden(aP, "Advanced content should not be visible by default") + }); + }); +}); diff --git a/browser/base/content/test/general/browser_bug647886.js b/browser/base/content/test/general/browser_bug647886.js new file mode 100644 index 000000000..6c28c465c --- /dev/null +++ b/browser/base/content/test/general/browser_bug647886.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + content.history.pushState({}, "2", "2.html"); + }); + + var backButton = document.getElementById("back-button"); + var rect = backButton.getBoundingClientRect(); + + info("waiting for the history menu to open"); + + let popupShownPromise = BrowserTestUtils.waitForEvent(backButton, "popupshown"); + EventUtils.synthesizeMouseAtCenter(backButton, {type: "mousedown"}); + EventUtils.synthesizeMouse(backButton, rect.width / 2, rect.height, {type: "mouseup"}); + let event = yield popupShownPromise; + + ok(true, "history menu opened"); + + // Wait for the session data to be flushed before continuing the test + yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)); + + is(event.target.children.length, 2, "Two history items"); + + let node = event.target.firstChild; + is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri"); + is(node.getAttribute("index"), "1", "first item index"); + is(node.getAttribute("historyindex"), "0", "first item historyindex"); + + node = event.target.lastChild; + is(node.getAttribute("uri"), "http://example.com/", "second item uri"); + is(node.getAttribute("index"), "0", "second item index"); + is(node.getAttribute("historyindex"), "-1", "second item historyindex"); + + event.target.hidePopup(); + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/general/browser_bug655584.js b/browser/base/content/test/general/browser_bug655584.js new file mode 100644 index 000000000..b836e3173 --- /dev/null +++ b/browser/base/content/test/general/browser_bug655584.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Bug 655584 - awesomebar suggestions don't update after tab is closed + +add_task(function* () { + var tab1 = gBrowser.addTab(); + var tab2 = gBrowser.addTab(); + + // When urlbar in a new tab is focused, and a tab switch occurs, + // the urlbar popup should be closed + yield BrowserTestUtils.switchTab(gBrowser, tab2); + gURLBar.focus(); // focus the urlbar in the tab we will switch to + yield BrowserTestUtils.switchTab(gBrowser, tab1); + gURLBar.openPopup(); + yield BrowserTestUtils.switchTab(gBrowser, tab2); + ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup"); + + // cleanup + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug664672.js b/browser/base/content/test/general/browser_bug664672.js new file mode 100644 index 000000000..2064f77d0 --- /dev/null +++ b/browser/base/content/test/general/browser_bug664672.js @@ -0,0 +1,19 @@ +function test() { + waitForExplicitFinish(); + + var tab = gBrowser.addTab(); + + tab.addEventListener("TabClose", function () { + tab.removeEventListener("TabClose", arguments.callee, false); + + ok(tab.linkedBrowser, "linkedBrowser should still exist during the TabClose event"); + + executeSoon(function () { + ok(!tab.linkedBrowser, "linkedBrowser should be gone after the TabClose event"); + + finish(); + }); + }, false); + + gBrowser.removeTab(tab); +} diff --git a/browser/base/content/test/general/browser_bug676619.js b/browser/base/content/test/general/browser_bug676619.js new file mode 100644 index 000000000..6b596481d --- /dev/null +++ b/browser/base/content/test/general/browser_bug676619.js @@ -0,0 +1,124 @@ +function test () { + requestLongerTimeout(3); + waitForExplicitFinish(); + + var isHTTPS = false; + + function loadListener() { + function testLocation(link, url, next) { + new TabOpenListener(url, function () { + gBrowser.removeTab(this.tab); + }, function () { + next(); + }); + + ContentTask.spawn(gBrowser.selectedBrowser, link, contentLink => { + content.document.getElementById(contentLink).click(); + }); + } + + function testLink(link, name, next) { + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function (win) { + ContentTask.spawn(gBrowser.selectedBrowser, null, () => { + Assert.equal(content.document.getElementById("unload-flag").textContent, + "Okay", "beforeunload shouldn't have fired"); + }).then(() => { + is(win.document.getElementById("location").value, name, "file name should match"); + win.close(); + next(); + }); + }); + + ContentTask.spawn(gBrowser.selectedBrowser, link, contentLink => { + content.document.getElementById(contentLink).click(); + }); + } + + testLink("link1", "test.txt", + testLink.bind(null, "link2", "video.ogg", + testLink.bind(null, "link3", "just some video", + testLink.bind(null, "link4", "with-target.txt", + testLink.bind(null, "link5", "javascript.txt", + testLink.bind(null, "link6", "test.blob", + testLocation.bind(null, "link7", "http://example.com/", + function () { + if (isHTTPS) { + finish(); + } else { + // same test again with https: + isHTTPS = true; + gBrowser.loadURI("https://example.com:443/browser/browser/base/content/test/general/download_page.html"); + } + }))))))); + + } + + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + loadListener(); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(loadListener); + }); + + gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html"); +} + + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow: function(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow: function(aXULWindow) { }, + onWindowTitleChange: function(aXULWindow, aNewTitle) { } + }); +} + +// This listens for the next opened tab and checks it is of the right url. +// opencallback is called when the new tab is fully loaded +// closecallback is called when the tab is closed +function TabOpenListener(url, opencallback, closecallback) { + this.url = url; + this.opencallback = opencallback; + this.closecallback = closecallback; + + gBrowser.tabContainer.addEventListener("TabOpen", this, false); +} + +TabOpenListener.prototype = { + url: null, + opencallback: null, + closecallback: null, + tab: null, + browser: null, + + handleEvent: function(event) { + if (event.type == "TabOpen") { + gBrowser.tabContainer.removeEventListener("TabOpen", this, false); + this.tab = event.originalTarget; + this.browser = this.tab.linkedBrowser; + BrowserTestUtils.browserLoaded(this.browser, false, this.url).then(() => { + this.tab.addEventListener("TabClose", this, false); + var url = this.browser.currentURI.spec; + is(url, this.url, "Should have opened the correct tab"); + this.opencallback(); + }); + } else if (event.type == "TabClose") { + if (event.originalTarget != this.tab) + return; + this.tab.removeEventListener("TabClose", this, false); + this.opencallback = null; + this.tab = null; + this.browser = null; + // Let the window close complete + executeSoon(this.closecallback); + this.closecallback = null; + } + } +}; diff --git a/browser/base/content/test/general/browser_bug678392-1.html b/browser/base/content/test/general/browser_bug678392-1.html new file mode 100644 index 000000000..c3b235dd0 --- /dev/null +++ b/browser/base/content/test/general/browser_bug678392-1.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> + <head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> + <title>bug678392 - 1</title> + </head> + <body> +bug 678392 test page 1 + </body> +</html>
\ No newline at end of file diff --git a/browser/base/content/test/general/browser_bug678392-2.html b/browser/base/content/test/general/browser_bug678392-2.html new file mode 100644 index 000000000..9b18efcf7 --- /dev/null +++ b/browser/base/content/test/general/browser_bug678392-2.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> + <head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> + <title>bug678392 - 2</title> + </head> + <body> +bug 678392 test page 2 + </body> +</html>
\ No newline at end of file diff --git a/browser/base/content/test/general/browser_bug678392.js b/browser/base/content/test/general/browser_bug678392.js new file mode 100644 index 000000000..6aedeefdf --- /dev/null +++ b/browser/base/content/test/general/browser_bug678392.js @@ -0,0 +1,191 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var HTTPROOT = "http://example.com/browser/browser/base/content/test/general/"; + +function maxSnapshotOverride() { + return 5; +} + +function test() { + waitForExplicitFinish(); + + BrowserOpenTab(); + let tab = gBrowser.selectedTab; + registerCleanupFunction(function () { gBrowser.removeTab(tab); }); + + ok(gHistorySwipeAnimation, "gHistorySwipeAnimation exists."); + + if (!gHistorySwipeAnimation._isSupported()) { + is(gHistorySwipeAnimation.active, false, "History swipe animation is not " + + "active when not supported by the platform."); + finish(); + return; + } + + gHistorySwipeAnimation._getMaxSnapshots = maxSnapshotOverride; + gHistorySwipeAnimation.init(); + + is(gHistorySwipeAnimation.active, true, "History swipe animation support " + + "was successfully initialized when supported."); + + cleanupArray(); + load(gBrowser.selectedTab, HTTPROOT + "browser_bug678392-2.html", test0); +} + +function load(aTab, aUrl, aCallback) { + aTab.linkedBrowser.addEventListener("load", function onload(aEvent) { + aEvent.currentTarget.removeEventListener("load", onload, true); + waitForFocus(aCallback, content); + }, true); + aTab.linkedBrowser.loadURI(aUrl); +} + +function cleanupArray() { + let arr = gHistorySwipeAnimation._trackedSnapshots; + while (arr.length > 0) { + delete arr[0].browser.snapshots[arr[0].index]; // delete actual snapshot + arr.splice(0, 1); + } +} + +function testArrayCleanup() { + // Test cleanup of array of tracked snapshots. + let arr = gHistorySwipeAnimation._trackedSnapshots; + is(arr.length, 0, "Snapshots were removed correctly from the array of " + + "tracked snapshots."); +} + +function test0() { + // Test growing of array of tracked snapshots. + let tab = gBrowser.selectedTab; + + load(tab, HTTPROOT + "browser_bug678392-1.html", function() { + ok(gHistorySwipeAnimation._trackedSnapshots, "Array for snapshot " + + "tracking is initialized."); + is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Snapshot array " + + "has correct length of 1 after loading one page."); + load(tab, HTTPROOT + "browser_bug678392-2.html", function() { + is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Snapshot array " + + " has correct length of 2 after loading two pages."); + load(tab, HTTPROOT + "browser_bug678392-1.html", function() { + is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Snapshot " + + "array has correct length of 3 after loading three pages."); + load(tab, HTTPROOT + "browser_bug678392-2.html", function() { + is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Snapshot " + + "array has correct length of 4 after loading four pages."); + cleanupArray(); + testArrayCleanup(); + test1(); + }); + }); + }); + }); +} + +function verifyRefRemoved(aIndex, aBrowser) { + let wasFound = false; + let arr = gHistorySwipeAnimation._trackedSnapshots; + for (let i = 0; i < arr.length; i++) { + if (arr[i].index == aIndex && arr[i].browser == aBrowser) + wasFound = true; + } + is(wasFound, false, "The reference that was previously removed was " + + "still found in the array of tracked snapshots."); +} + +function test1() { + // Test presence of snpashots in per-tab array of snapshots and removal of + // individual snapshots (and corresponding references in the array of + // tracked snapshots). + let tab = gBrowser.selectedTab; + + load(tab, HTTPROOT + "browser_bug678392-1.html", function() { + var historyIndex = gBrowser.webNavigation.sessionHistory.index - 1; + load(tab, HTTPROOT + "browser_bug678392-2.html", function() { + load(tab, HTTPROOT + "browser_bug678392-1.html", function() { + load(tab, HTTPROOT + "browser_bug678392-2.html", function() { + let browser = gBrowser.selectedBrowser; + ok(browser.snapshots, "Array of snapshots exists in browser."); + ok(browser.snapshots[historyIndex], "First page exists in snapshot " + + "array."); + ok(browser.snapshots[historyIndex + 1], "Second page exists in " + + "snapshot array."); + ok(browser.snapshots[historyIndex + 2], "Third page exists in " + + "snapshot array."); + ok(browser.snapshots[historyIndex + 3], "Fourth page exists in " + + "snapshot array."); + is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length of " + + "array of tracked snapshots is equal to 4 after loading four " + + "pages."); + + // Test removal of reference in the middle of the array. + gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 1, + browser); + verifyRefRemoved(historyIndex + 1, browser); + is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Length of " + + "array of tracked snapshots is equal to 3 after removing one" + + "reference from the array with length 4."); + + // Test removal of reference at end of array. + gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 3, + browser); + verifyRefRemoved(historyIndex + 3, browser); + is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " + + "array of tracked snapshots is equal to 2 after removing two" + + "references from the array with length 4."); + + // Test removal of reference at head of array. + gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex, + browser); + verifyRefRemoved(historyIndex, browser); + is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Length of " + + "array of tracked snapshots is equal to 1 after removing three" + + "references from the array with length 4."); + + cleanupArray(); + test2(); + }); + }); + }); + }); +} + +function test2() { + // Test growing of snapshot array across tabs. + let tab = gBrowser.selectedTab; + + load(tab, HTTPROOT + "browser_bug678392-1.html", function() { + load(tab, HTTPROOT + "browser_bug678392-2.html", function() { + is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " + + "snapshot array is equal to 2 after loading two pages"); + let prevTab = tab; + tab = gBrowser.addTab("about:newtab"); + gBrowser.selectedTab = tab; + load(tab, HTTPROOT + "browser_bug678392-2.html" /* initial page */, + function() { + load(tab, HTTPROOT + "browser_bug678392-1.html", function() { + load(tab, HTTPROOT + "browser_bug678392-2.html", function() { + is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length " + + "of snapshot array is equal to 4 after loading two pages in " + + "two tabs each."); + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = prevTab; + cleanupArray(); + test3(); + }); + }); + }); + }); + }); +} + +function test3() { + // Test uninit of gHistorySwipeAnimation. + // This test MUST be the last one to execute. + gHistorySwipeAnimation.uninit(); + is(gHistorySwipeAnimation.active, false, "History swipe animation support " + + "was successfully uninitialized"); + finish(); +} diff --git a/browser/base/content/test/general/browser_bug710878.js b/browser/base/content/test/general/browser_bug710878.js new file mode 100644 index 000000000..dd99d67cf --- /dev/null +++ b/browser/base/content/test/general/browser_bug710878.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const PAGE = "data:text/html;charset=utf-8,<a href='%23xxx'><span>word1 <span> word2 </span></span><span> word3</span></a>"; + +/** + * Tests that we correctly compute the text for context menu + * selection of some content. + */ +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + let contextMenu = document.getElementById("contentAreaContextMenu"); + let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, + "popupshown"); + let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, + "popuphidden"); + + yield BrowserTestUtils.synthesizeMouseAtCenter("a", { + type: "contextmenu", + button: 2, + }, browser); + + yield awaitPopupShown; + + is(gContextMenu.linkTextStr, "word1 word2 word3", + "Text under link is correctly computed."); + + contextMenu.hidePopup(); + yield awaitPopupHidden; + }); +}); diff --git a/browser/base/content/test/general/browser_bug719271.js b/browser/base/content/test/general/browser_bug719271.js new file mode 100644 index 000000000..c3bb9cd26 --- /dev/null +++ b/browser/base/content/test/general/browser_bug719271.js @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html"; +const TEST_VIDEO = "http://example.org/browser/browser/base/content/test/general/video.ogg"; + +var gTab1, gTab2, gLevel1; + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + gTab1 = gBrowser.addTab(); + gTab2 = gBrowser.addTab(); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + yield FullZoomHelper.load(gTab1, TEST_PAGE); + yield FullZoomHelper.load(gTab2, TEST_VIDEO); + }).then(zoomTab1, FullZoomHelper.failAndContinue(finish)); +} + +function zoomTab1() { + Task.spawn(function* () { + is(gBrowser.selectedTab, gTab1, "Tab 1 is selected"); + + // Reset zoom level if we run this test > 1 time in same browser session. + var level1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1)); + if (level1 > 1) + FullZoom.reduce(); + + FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1"); + FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1"); + + FullZoom.enlarge(); + gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1)); + + ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1"); + FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2"); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2); + FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected"); + FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed"); + }).then(zoomTab2, FullZoomHelper.failAndContinue(finish)); +} + +function zoomTab2() { + Task.spawn(function* () { + is(gBrowser.selectedTab, gTab2, "Tab 2 is selected"); + + FullZoom.reduce(); + let level2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2)); + + ok(level2 < 1, "New zoom for tab 2 should be less than 1"); + FullZoomHelper.zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1"); + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected"); + }).then(testNavigation, FullZoomHelper.failAndContinue(finish)); +} + +function testNavigation() { + Task.spawn(function* () { + yield FullZoomHelper.load(gTab1, TEST_VIDEO); + FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when a video was loaded"); + yield waitForNextTurn(); // trying to fix orange bug 806046 + yield FullZoomHelper.navigate(FullZoomHelper.BACK); + FullZoomHelper.zoomTest(gTab1, gLevel1, "Zoom should be restored when a page is loaded"); + yield waitForNextTurn(); // trying to fix orange bug 806046 + yield FullZoomHelper.navigate(FullZoomHelper.FORWARD); + FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 again when navigating back to a video"); + }).then(finishTest, FullZoomHelper.failAndContinue(finish)); +} + +function waitForNextTurn() { + let deferred = Promise.defer(); + setTimeout(() => deferred.resolve(), 0); + return deferred.promise; +} + +var finishTestStarted = false; +function finishTest() { + Task.spawn(function* () { + ok(!finishTestStarted, "finishTest called more than once"); + finishTestStarted = true; + + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1); + yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2); + yield FullZoom.reset(); + yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2); + }).then(finish, FullZoomHelper.failAndContinue(finish)); +} diff --git a/browser/base/content/test/general/browser_bug724239.js b/browser/base/content/test/general/browser_bug724239.js new file mode 100644 index 000000000..430751b91 --- /dev/null +++ b/browser/base/content/test/general/browser_bug724239.js @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* test() { + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, + function* (browser) { + BrowserTestUtils.loadURI(browser, "http://example.com"); + yield BrowserTestUtils.browserLoaded(browser); + ok(!gBrowser.canGoBack, "about:newtab wasn't added to the session history"); + }); +}); diff --git a/browser/base/content/test/general/browser_bug734076.js b/browser/base/content/test/general/browser_bug734076.js new file mode 100644 index 000000000..9de7d913f --- /dev/null +++ b/browser/base/content/test/general/browser_bug734076.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () +{ + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, null, false); + + let browser = tab.linkedBrowser; + browser.stop(); // stop the about:blank load + + let writeDomainURL = encodeURI("data:text/html,<script>document.write(document.domain);</script>"); + + let tests = [ + { + name: "view background image", + url: "http://mochi.test:8888/", + element: "body", + go: function () { + return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) { + let contentBody = content.document.body; + contentBody.style.backgroundImage = "url('" + arg.writeDomainURL + "')"; + + return "context-viewbgimage"; + }); + }, + verify: function () { + return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) { + Assert.ok(!content.document.body.textContent, + "no domain was inherited for view background image"); + }); + } + }, + { + name: "view image", + url: "http://mochi.test:8888/", + element: "img", + go: function () { + return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) { + let doc = content.document; + let img = doc.createElement("img"); + img.height = 100; + img.width = 100; + img.setAttribute("src", arg.writeDomainURL); + doc.body.insertBefore(img, doc.body.firstChild); + + return "context-viewimage"; + }); + }, + verify: function () { + return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) { + Assert.ok(!content.document.body.textContent, + "no domain was inherited for view image"); + }); + } + }, + { + name: "show only this frame", + url: "http://mochi.test:8888/", + element: "iframe", + go: function () { + return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) { + let doc = content.document; + let iframe = doc.createElement("iframe"); + iframe.setAttribute("src", arg.writeDomainURL); + doc.body.insertBefore(iframe, doc.body.firstChild); + + // Wait for the iframe to load. + return new Promise(resolve => { + iframe.addEventListener("load", function onload() { + iframe.removeEventListener("load", onload, true); + resolve("context-showonlythisframe"); + }, true); + }); + }); + }, + verify: function () { + return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) { + Assert.ok(!content.document.body.textContent, + "no domain was inherited for 'show only this frame'"); + }); + } + } + ]; + + let contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + + for (let test of tests) { + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(test.url); + yield loadedPromise; + + info("Run subtest " + test.name); + let commandToRun = yield test.go(); + + let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouse(test.element, 3, 3, + { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser); + yield popupShownPromise; + info("onImage: " + gContextMenu.onImage); + info("target: " + gContextMenu.target.tagName); + + let loadedAfterCommandPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + document.getElementById(commandToRun).click(); + yield loadedAfterCommandPromise; + + yield test.verify(); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden"); + contentAreaContextMenu.hidePopup(); + yield popupHiddenPromise; + } + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug735471.js b/browser/base/content/test/general/browser_bug735471.js new file mode 100644 index 000000000..9afb52c4b --- /dev/null +++ b/browser/base/content/test/general/browser_bug735471.js @@ -0,0 +1,23 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + + +function test() { + waitForExplicitFinish(); + // Open a new tab. + whenNewTabLoaded(window, testPreferences); +} + +function testPreferences() { + whenTabLoaded(gBrowser.selectedTab, function () { + is(content.location.href, "about:preferences", "Checking if the preferences tab was opened"); + + gBrowser.removeCurrentTab(); + finish(); + }); + + openPreferences(); +} diff --git a/browser/base/content/test/general/browser_bug749738.js b/browser/base/content/test/general/browser_bug749738.js new file mode 100644 index 000000000..7e805b799 --- /dev/null +++ b/browser/base/content/test/general/browser_bug749738.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const DUMMY_PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + + BrowserTestUtils.loadURI(tab.linkedBrowser, DUMMY_PAGE); + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + gFindBar.onFindCommand(); + EventUtils.sendString("Dummy"); + gBrowser.removeTab(tab); + + try { + gFindBar.close(); + ok(true, "findbar.close should not throw an exception"); + } catch (e) { + ok(false, "findbar.close threw exception: " + e); + } + finish(); + }); +} diff --git a/browser/base/content/test/general/browser_bug763468_perwindowpb.js b/browser/base/content/test/general/browser_bug763468_perwindowpb.js new file mode 100644 index 000000000..23cb14b8c --- /dev/null +++ b/browser/base/content/test/general/browser_bug763468_perwindowpb.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +/* globals + waitForExplicitFinish, whenNewWindowLoaded, whenNewTabLoaded, + executeSoon, registerCleanupFunction, finish, is +*/ +/* exported test */ + +// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing +function test() { + // initialization + waitForExplicitFinish(); + + let windowsToClose = []; + let newTabURL; + let mode; + + function doTest(aIsPrivateMode, aWindow, aCallback) { + whenNewTabLoaded(aWindow, function() { + if (aIsPrivateMode) { + mode = "per window private browsing"; + newTabURL = "about:privatebrowsing"; + } else { + mode = "normal"; + newTabURL = "about:newtab"; + } + + is(aWindow.gBrowser.currentURI.spec, newTabURL, + "URL of NewTab should be " + newTabURL + " in " + mode + " mode"); + + aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab); + aCallback(); + }); + } + + function testOnWindow(aOptions, aCallback) { + whenNewWindowLoaded(aOptions, function(aWin) { + windowsToClose.push(aWin); + // execute should only be called when need, like when you are opening + // web pages on the test. If calling executeSoon() is not necesary, then + // call whenNewWindowLoaded() instead of testOnWindow() on your test. + executeSoon(() => aCallback(aWin)); + }); + } + + // this function is called after calling finish() on the test. + registerCleanupFunction(function() { + windowsToClose.forEach(function(aWin) { + aWin.close(); + }); + }); + + // test first when not on private mode + testOnWindow({}, function(aWin) { + doTest(false, aWin, function() { + // then test when on private mode + testOnWindow({private: true}, function(aWin2) { + doTest(true, aWin2, function() { + // then test again when not on private mode + testOnWindow({}, function(aWin3) { + doTest(false, aWin3, finish); + }); + }); + }); + }); + }); +} diff --git a/browser/base/content/test/general/browser_bug767836_perwindowpb.js b/browser/base/content/test/general/browser_bug767836_perwindowpb.js new file mode 100644 index 000000000..7f5d15e76 --- /dev/null +++ b/browser/base/content/test/general/browser_bug767836_perwindowpb.js @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; +/* globals waitForExplicitFinish, executeSoon, finish, whenNewWindowLoaded, ok */ +/* globals is */ +/* exported test */ + +function test() { + // initialization + waitForExplicitFinish(); + + let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"] + .getService(Components.interfaces.nsIAboutNewTabService); + let newTabURL; + let testURL = "http://example.com/"; + let defaultURL = aboutNewTabService.newTabURL; + let mode; + + function doTest(aIsPrivateMode, aWindow, aCallback) { + openNewTab(aWindow, function() { + if (aIsPrivateMode) { + mode = "per window private browsing"; + newTabURL = "about:privatebrowsing"; + } else { + mode = "normal"; + newTabURL = "about:newtab"; + } + + // Check the new tab opened while in normal/private mode + is(aWindow.gBrowser.selectedBrowser.currentURI.spec, newTabURL, + "URL of NewTab should be " + newTabURL + " in " + mode + " mode"); + // Set the custom newtab url + aboutNewTabService.newTabURL = testURL; + is(aboutNewTabService.newTabURL, testURL, "Custom newtab url is set"); + + // Open a newtab after setting the custom newtab url + openNewTab(aWindow, function() { + is(aWindow.gBrowser.selectedBrowser.currentURI.spec, testURL, + "URL of NewTab should be the custom url"); + + // Clear the custom url. + aboutNewTabService.resetNewTabURL(); + is(aboutNewTabService.newTabURL, defaultURL, "No custom newtab url is set"); + + aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab); + aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab); + aWindow.close(); + aCallback(); + }); + }); + } + + function testOnWindow(aIsPrivate, aCallback) { + whenNewWindowLoaded({private: aIsPrivate}, function(win) { + executeSoon(() => aCallback(win)); + }); + } + + // check whether any custom new tab url has been configured + ok(!aboutNewTabService.overridden, "No custom newtab url is set"); + + // test normal mode + testOnWindow(false, function(aWindow) { + doTest(false, aWindow, function() { + // test private mode + testOnWindow(true, function(aWindow2) { + doTest(true, aWindow2, function() { + finish(); + }); + }); + }); + }); +} + +function openNewTab(aWindow, aCallback) { + // Open a new tab + aWindow.BrowserOpenTab(); + + let browser = aWindow.gBrowser.selectedBrowser; + if (browser.contentDocument.readyState === "complete") { + executeSoon(aCallback); + return; + } + + browser.addEventListener("load", function onLoad() { + browser.removeEventListener("load", onLoad, true); + executeSoon(aCallback); + }, true); +} diff --git a/browser/base/content/test/general/browser_bug817947.js b/browser/base/content/test/general/browser_bug817947.js new file mode 100644 index 000000000..3a76e36d3 --- /dev/null +++ b/browser/base/content/test/general/browser_bug817947.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + + +const URL = "http://mochi.test:8888/browser/"; +const PREF = "browser.sessionstore.restore_on_demand"; + +function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref(PREF, true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF); + }); + + preparePendingTab(function (aTab) { + let win = gBrowser.replaceTabWithWindow(aTab); + + whenDelayedStartupFinished(win, function () { + let [tab] = win.gBrowser.tabs; + + whenLoaded(tab.linkedBrowser, function () { + is(tab.linkedBrowser.currentURI.spec, URL, "correct url should be loaded"); + ok(!tab.hasAttribute("pending"), "tab should not be pending"); + + win.close(); + finish(); + }); + }); + }); +} + +function preparePendingTab(aCallback) { + let tab = gBrowser.addTab(URL); + + whenLoaded(tab.linkedBrowser, function () { + BrowserTestUtils.removeTab(tab).then(() => { + let [{state}] = JSON.parse(SessionStore.getClosedTabData(window)); + + tab = gBrowser.addTab("about:blank"); + whenLoaded(tab.linkedBrowser, function () { + SessionStore.setTabState(tab, JSON.stringify(state)); + ok(tab.hasAttribute("pending"), "tab should be pending"); + aCallback(tab); + }); + }); + }); +} + +function whenLoaded(aElement, aCallback) { + aElement.addEventListener("load", function onLoad() { + aElement.removeEventListener("load", onLoad, true); + executeSoon(aCallback); + }, true); +} diff --git a/browser/base/content/test/general/browser_bug822367.js b/browser/base/content/test/general/browser_bug822367.js new file mode 100644 index 000000000..0d60c05cd --- /dev/null +++ b/browser/base/content/test/general/browser_bug822367.js @@ -0,0 +1,187 @@ +/* + * User Override Mixed Content Block - Tests for Bug 822367 + */ + + +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_ACTIVE = "security.mixed_content.block_active_content"; + +// We alternate for even and odd test cases to simulate different hosts +const gHttpTestRoot = "https://example.com/browser/browser/base/content/test/general/"; +const gHttpTestRoot2 = "https://test1.example.com/browser/browser/base/content/test/general/"; + +var gTestBrowser = null; + +add_task(function* test() { + yield SpecialPowers.pushPrefEnv({ set: [[ PREF_DISPLAY, true ], + [ PREF_ACTIVE, true ]] }); + + var newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop() + + // Mixed Script Test + var url = gHttpTestRoot + "file_bug822367_1.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +// Mixed Script Test +add_task(function* MixedTest1A() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest1B() { + yield ContentTask.spawn(gTestBrowser, null, function* () { + yield ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "hello", + "Waited too long for mixed script to run in Test 1"); + }); +}); + +// Mixed Display Test - Doorhanger should not appear +add_task(function* MixedTest2() { + var url = gHttpTestRoot2 + "file_bug822367_2.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); + + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false}); +}); + +// Mixed Script and Display Test - User Override should cause both the script and the image to load. +add_task(function* MixedTest3() { + var url = gHttpTestRoot + "file_bug822367_3.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest3A() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest3B() { + yield ContentTask.spawn(gTestBrowser, null, function* () { + let p1 = ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "hello", + "Waited too long for mixed script to run in Test 3"); + let p2 = ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p2").innerHTML == "bye", + "Waited too long for mixed image to load in Test 3"); + yield Promise.all([ p1, p2 ]); + }); + + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: true}); +}); + +// Location change - User override on one page doesn't propogate to another page after location change. +add_task(function* MixedTest4() { + var url = gHttpTestRoot2 + "file_bug822367_4.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest4A() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest4B() { + let url = gHttpTestRoot + "file_bug822367_4B.html"; + yield ContentTask.spawn(gTestBrowser, url, function* (wantedUrl) { + yield ContentTaskUtils.waitForCondition( + () => content.document.location == wantedUrl, + "Waited too long for mixed script to run in Test 4"); + }); +}); + +add_task(function* MixedTest4C() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + yield ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "", + "Mixed script loaded in test 4 after location change!"); + }); +}); + +// Mixed script attempts to load in a document.open() +add_task(function* MixedTest5() { + var url = gHttpTestRoot + "file_bug822367_5.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest5A() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest5B() { + yield ContentTask.spawn(gTestBrowser, null, function* () { + yield ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "hello", + "Waited too long for mixed script to run in Test 5"); + }); +}); + +// Mixed script attempts to load in a document.open() that is within an iframe. +add_task(function* MixedTest6() { + var url = gHttpTestRoot2 + "file_bug822367_6.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest6A() { + gTestBrowser.removeEventListener("load", MixedTest6A, true); + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + + yield BrowserTestUtils.waitForCondition( + () => gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"), + "Waited too long for control center to get mixed active blocked state"); +}); + +add_task(function* MixedTest6B() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* MixedTest6C() { + yield ContentTask.spawn(gTestBrowser, null, function* () { + function test() { + try { + return content.document.getElementById("f1").contentDocument.getElementById("p1").innerHTML == "hello"; + } catch (e) { + return false; + } + } + + yield ContentTaskUtils.waitForCondition(test, "Waited too long for mixed script to run in Test 6"); + }); +}); + +add_task(function* MixedTest6D() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false}); +}); + +add_task(function* cleanup() { + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug832435.js b/browser/base/content/test/general/browser_bug832435.js new file mode 100644 index 000000000..6be2604cd --- /dev/null +++ b/browser/base/content/test/general/browser_bug832435.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + ok(true, "Starting up"); + + gBrowser.selectedBrowser.focus(); + gURLBar.addEventListener("focus", function onFocus() { + gURLBar.removeEventListener("focus", onFocus); + ok(true, "Invoked onfocus handler"); + EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }); + + // javscript: URIs are evaluated async. + SimpleTest.executeSoon(function() { + ok(true, "Evaluated without crashing"); + finish(); + }); + }); + gURLBar.inputField.value = "javascript: var foo = '11111111'; "; + gURLBar.focus(); +} diff --git a/browser/base/content/test/general/browser_bug839103.js b/browser/base/content/test/general/browser_bug839103.js new file mode 100644 index 000000000..5240c92ed --- /dev/null +++ b/browser/base/content/test/general/browser_bug839103.js @@ -0,0 +1,120 @@ +const gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +add_task(function* test() { + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, + function* (browser) { + yield ContentTask.spawn(browser, gTestRoot, testBody); + }); +}); + +// This function runs entirely in the content process. It doesn't have access +// any free variables in this file. +function* testBody(testRoot) { + const gStyleSheet = "bug839103.css"; + + let loaded = ContentTaskUtils.waitForEvent(this, "load", true); + content.location = testRoot + "test_bug839103.html"; + + yield loaded; + function unexpectedContentEvent(event) { + ok(false, "Received a " + event.type + " event on content"); + } + + // We've seen the original stylesheet in the document. + // Now add a stylesheet on the fly and make sure we see it. + let doc = content.document; + doc.styleSheetChangeEventsEnabled = true; + doc.addEventListener("StyleSheetAdded", unexpectedContentEvent); + doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent); + doc.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent); + doc.defaultView.addEventListener("StyleSheetAdded", unexpectedContentEvent); + doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent); + doc.defaultView.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent); + + let link = doc.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", testRoot + gStyleSheet); + + let sheetAdded = + ContentTaskUtils.waitForEvent(this, "StyleSheetAdded", true); + let stateChanged = + ContentTaskUtils.waitForEvent(this, "StyleSheetApplicableStateChanged", true); + doc.body.appendChild(link); + + let evt = yield sheetAdded; + info("received dynamic style sheet event"); + is(evt.type, "StyleSheetAdded", "evt.type has expected value"); + is(evt.target, doc, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.documentSheet, "style sheet is a document sheet"); + + evt = yield stateChanged; + info("received dynamic style sheet applicable state change event"); + is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value"); + is(evt.target, doc, "event targets correct document"); + is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value"); + is(evt.applicable, true, "evt.applicable has the right value"); + + stateChanged = + ContentTaskUtils.waitForEvent(this, "StyleSheetApplicableStateChanged", true); + link.disabled = true; + + evt = yield stateChanged; + is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value"); + info("received dynamic style sheet applicable state change event after media=\"\" changed"); + is(evt.target, doc, "event targets correct document"); + is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value"); + is(evt.applicable, false, "evt.applicable has the right value"); + + let sheetRemoved = + ContentTaskUtils.waitForEvent(this, "StyleSheetRemoved", true); + doc.body.removeChild(link); + + evt = yield sheetRemoved; + info("received dynamic style sheet removal"); + is(evt.type, "StyleSheetRemoved", "evt.type has expected value"); + is(evt.target, doc, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.stylesheet.href.includes(gStyleSheet), "evt.stylesheet is the removed stylesheet"); + + let ruleAdded = + ContentTaskUtils.waitForEvent(this, "StyleRuleAdded", true); + doc.querySelector("style").sheet.insertRule("*{color:black}", 0); + + evt = yield ruleAdded; + info("received style rule added event"); + is(evt.type, "StyleRuleAdded", "evt.type has expected value"); + is(evt.target, doc, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.rule, "evt.rule is defined"); + is(evt.rule.cssText, "* { color: black; }", "evt.rule.cssText has expected value"); + + let ruleChanged = + ContentTaskUtils.waitForEvent(this, "StyleRuleChanged", true); + evt.rule.style.cssText = "color:green"; + + evt = yield ruleChanged; + ok(true, "received style rule changed event"); + is(evt.type, "StyleRuleChanged", "evt.type has expected value"); + is(evt.target, doc, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.rule, "evt.rule is defined"); + is(evt.rule.cssText, "* { color: green; }", "evt.rule.cssText has expected value"); + + let ruleRemoved = + ContentTaskUtils.waitForEvent(this, "StyleRuleRemoved", true); + evt.stylesheet.deleteRule(0); + + evt = yield ruleRemoved; + info("received style rule removed event"); + is(evt.type, "StyleRuleRemoved", "evt.type has expected value"); + is(evt.target, doc, "event targets correct document"); + ok(evt.stylesheet, "evt.stylesheet is defined"); + ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet"); + ok(evt.rule, "evt.rule is defined"); +} diff --git a/browser/base/content/test/general/browser_bug882977.js b/browser/base/content/test/general/browser_bug882977.js new file mode 100644 index 000000000..ed958e06b --- /dev/null +++ b/browser/base/content/test/general/browser_bug882977.js @@ -0,0 +1,29 @@ +"use strict"; + +/** + * Tests that the identity-box shows the chromeUI styling + * when viewing about:home in a new window. + */ +add_task(function*() { + let homepage = "about:home"; + yield SpecialPowers.pushPrefEnv({ + "set": [ + ["browser.startup.homepage", homepage], + ["browser.startup.page", 1], + ] + }); + + let win = OpenBrowserWindow(); + yield BrowserTestUtils.firstBrowserLoaded(win, false); + + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, homepage, "Loaded the correct homepage"); + checkIdentityMode(win); + + yield BrowserTestUtils.closeWindow(win); +}); + +function checkIdentityMode(win) { + let identityMode = win.document.getElementById("identity-box").className; + is(identityMode, "chromeUI", "Identity state should be chromeUI for about:home in a new window"); +} diff --git a/browser/base/content/test/general/browser_bug902156.js b/browser/base/content/test/general/browser_bug902156.js new file mode 100644 index 000000000..74969ead4 --- /dev/null +++ b/browser/base/content/test/general/browser_bug902156.js @@ -0,0 +1,174 @@ +/* + * Description of the Tests for + * - Bug 902156: Persist "disable protection" option for Mixed Content Blocker + * + * 1. Navigate to the same domain via document.location + * - Load a html page which has mixed content + * - Control Center button to disable protection appears - we disable it + * - Load a new page from the same origin using document.location + * - Control Center button should not appear anymore! + * + * 2. Navigate to the same domain via simulateclick for a link on the page + * - Load a html page which has mixed content + * - Control Center button to disable protection appears - we disable it + * - Load a new page from the same origin simulating a click + * - Control Center button should not appear anymore! + * + * 3. Navigate to a differnet domain and show the content is still blocked + * - Load a different html page which has mixed content + * - Control Center button to disable protection should appear again because + * we navigated away from html page where we disabled the protection. + * + * Note, for all tests we set gHttpTestRoot to use 'https'. + */ + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; + +// We alternate for even and odd test cases to simulate different hosts +const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/"; +const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/"; + +var origBlockActive; +var gTestBrowser = null; + +registerCleanupFunction(function() { + // Set preferences back to their original values + Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive); +}); + +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); + finish(); +} + +// ------------------------ Test 1 ------------------------------ + +function test1A() { + BrowserTestUtils.browserLoaded(gTestBrowser).then(test1B); + + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + // Disable Mixed Content Protection for the page (and reload) + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); +} + +function test1B() { + var expected = "Mixed Content Blocker disabled"; + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test1C, "Error: Waited too long for mixed script to run in Test 1B"); +} + +function test1C() { + var actual = content.document.getElementById('mctestdiv').innerHTML; + is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1C"); + + // The Script loaded after we disabled the page, now we are going to reload the + // page and see if our decision is persistent + BrowserTestUtils.browserLoaded(gTestBrowser).then(test1D); + + var url = gHttpTestRoot1 + "file_bug902156_2.html"; + gTestBrowser.loadURI(url); +} + +function test1D() { + // The Control Center button should appear but isMixedContentBlocked should be NOT true, + // because our decision of disabling the mixed content blocker is persistent. + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false}); + + var actual = content.document.getElementById('mctestdiv').innerHTML; + is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1D"); + + // move on to Test 2 + test2(); +} + +// ------------------------ Test 2 ------------------------------ + +function test2() { + BrowserTestUtils.browserLoaded(gTestBrowser).then(test2A); + var url = gHttpTestRoot2 + "file_bug902156_2.html"; + gTestBrowser.loadURI(url); +} + +function test2A() { + BrowserTestUtils.browserLoaded(gTestBrowser).then(test2B); + + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + // Disable Mixed Content Protection for the page (and reload) + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); +} + +function test2B() { + var expected = "Mixed Content Blocker disabled"; + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test2C, "Error: Waited too long for mixed script to run in Test 2B"); +} + +function test2C() { + var actual = content.document.getElementById('mctestdiv').innerHTML; + is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2C"); + + // The Script loaded after we disabled the page, now we are going to reload the + // page and see if our decision is persistent + BrowserTestUtils.browserLoaded(gTestBrowser).then(test2D); + + // reload the page using the provided link in the html file + var mctestlink = content.document.getElementById("mctestlink"); + mctestlink.click(); +} + +function test2D() { + // The Control Center button should appear but isMixedContentBlocked should be NOT true, + // because our decision of disabling the mixed content blocker is persistent. + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false}); + + var actual = content.document.getElementById('mctestdiv').innerHTML; + is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2D"); + + // move on to Test 3 + test3(); +} + +// ------------------------ Test 3 ------------------------------ + +function test3() { + BrowserTestUtils.browserLoaded(gTestBrowser).then(test3A); + var url = gHttpTestRoot1 + "file_bug902156_3.html"; + gTestBrowser.loadURI(url); +} + +function test3A() { + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + // We are done with tests, clean up + cleanUpAfterTests(); +} + +// ------------------------------------------------------ + +function test() { + // Performing async calls, e.g. 'onload', we have to wait till all of them finished + waitForExplicitFinish(); + + // Store original preferences so we can restore settings after testing + origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE); + + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + // Not really sure what this is doing + var newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop() + + // Starting Test Number 1: + BrowserTestUtils.browserLoaded(gTestBrowser).then(test1A); + var url = gHttpTestRoot1 + "file_bug902156_1.html"; + gTestBrowser.loadURI(url); +} diff --git a/browser/base/content/test/general/browser_bug906190.js b/browser/base/content/test/general/browser_bug906190.js new file mode 100644 index 000000000..613f50efd --- /dev/null +++ b/browser/base/content/test/general/browser_bug906190.js @@ -0,0 +1,240 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests the persistence of the "disable protection" option for Mixed Content + * Blocker in child tabs (bug 906190). + */ + +requestLongerTimeout(2); + +// We use the different urls for testing same origin checks before allowing +// mixed content on child tabs. +const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/"; +const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/"; + +/** + * For all tests, we load the pages over HTTPS and test both: + * - |CTRL+CLICK| + * - |RIGHT CLICK -> OPEN LINK IN TAB| + */ +function* doTest(parentTabSpec, childTabSpec, testTaskFn, waitForMetaRefresh) { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: parentTabSpec, + }, function* (browser) { + // As a sanity check, test that active content has been blocked as expected. + yield assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, activeBlocked: true, passiveLoaded: false, + }); + + // Disable the Mixed Content Blocker for the page, which reloads it. + let promiseReloaded = BrowserTestUtils.browserLoaded(browser); + gIdentityHandler.disableMixedContentProtection(); + yield promiseReloaded; + + // Wait for the script in the page to update the contents of the test div. + let testDiv = content.document.getElementById('mctestdiv'); + yield promiseWaitForCondition( + () => testDiv.innerHTML == "Mixed Content Blocker disabled"); + + // Add the link for the child tab to the page. + let mainDiv = content.document.createElement("div"); + mainDiv.innerHTML = + '<p><a id="linkToOpenInNewTab" href="' + childTabSpec + '">Link</a></p>'; + content.document.body.appendChild(mainDiv); + + // Execute the test in the child tabs with the two methods to open it. + for (let openFn of [simulateCtrlClick, simulateContextMenuOpenInTab]) { + let promiseTabLoaded = waitForSomeTabToLoad(); + openFn(browser); + yield promiseTabLoaded; + gBrowser.selectTabAtIndex(2); + + if (waitForMetaRefresh) { + yield waitForSomeTabToLoad(); + } + + yield testTaskFn(); + + gBrowser.removeCurrentTab(); + } + }); +} + +function simulateCtrlClick(browser) { + BrowserTestUtils.synthesizeMouseAtCenter("#linkToOpenInNewTab", + { ctrlKey: true, metaKey: true }, + browser); +} + +function simulateContextMenuOpenInTab(browser) { + BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { + // These are operations that must be executed synchronously with the event. + document.getElementById("context-openlinkintab").doCommand(); + event.target.hidePopup(); + return true; + }); + BrowserTestUtils.synthesizeMouseAtCenter("#linkToOpenInNewTab", + { type: "contextmenu", button: 2 }, + browser); +} + +// Waits for a load event somewhere in the browser but ignore events coming +// from <xul:browser>s without a tab assigned. That are most likely browsers +// that preload the new tab page. +function waitForSomeTabToLoad() { + return new Promise(resolve => { + gBrowser.addEventListener("load", function onLoad(event) { + let tab = gBrowser._getTabForContentWindow(event.target.defaultView.top); + if (tab) { + gBrowser.removeEventListener("load", onLoad, true); + resolve(); + } + }, true); + }); +} + +/** + * Ensure the Mixed Content Blocker is enabled. + */ +add_task(function* test_initialize() { + yield new Promise(resolve => SpecialPowers.pushPrefEnv({ + "set": [["security.mixed_content.block_active_content", true]], + }, resolve)); +}); + +/** + * 1. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a subpage from the same origin in a new tab simulating a click + * - Doorhanger should >> NOT << appear anymore! + */ +add_task(function* test_same_origin() { + yield doTest(gHttpTestRoot1 + "file_bug906190_1.html", + gHttpTestRoot1 + "file_bug906190_2.html", function* () { + // The doorhanger should appear but activeBlocked should be >> NOT << true, + // because our decision of disabling the mixed content blocker is persistent + // across tabs. + yield assertMixedContentBlockingState(gBrowser, { + activeLoaded: true, activeBlocked: false, passiveLoaded: false, + }); + + is(content.document.getElementById('mctestdiv').innerHTML, + "Mixed Content Blocker disabled", "OK: Executed mixed script"); + }); +}); + +/** + * 2. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from a different origin in a new tab simulating a click + * - Doorhanger >> SHOULD << appear again! + */ +add_task(function* test_different_origin() { + yield doTest(gHttpTestRoot1 + "file_bug906190_2.html", + gHttpTestRoot2 + "file_bug906190_2.html", function* () { + // The doorhanger should appear and activeBlocked should be >> TRUE <<, + // because our decision of disabling the mixed content blocker should only + // persist if pages are from the same domain. + yield assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, activeBlocked: true, passiveLoaded: false, + }); + + is(content.document.getElementById('mctestdiv').innerHTML, + "Mixed Content Blocker enabled", "OK: Blocked mixed script"); + }); +}); + +/** + * 3. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from the same origin using meta-refresh + * - Doorhanger should >> NOT << appear again! + */ +add_task(function* test_same_origin_metarefresh_same_origin() { + // file_bug906190_3_4.html redirects to page test1.example.com/* using meta-refresh + yield doTest(gHttpTestRoot1 + "file_bug906190_1.html", + gHttpTestRoot1 + "file_bug906190_3_4.html", function* () { + // The doorhanger should appear but activeBlocked should be >> NOT << true! + yield assertMixedContentBlockingState(gBrowser, { + activeLoaded: true, activeBlocked: false, passiveLoaded: false, + }); + + is(content.document.getElementById('mctestdiv').innerHTML, + "Mixed Content Blocker disabled", "OK: Executed mixed script"); + }, true); +}); + +/** + * 4. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from a different origin using meta-refresh + * - Doorhanger >> SHOULD << appear again! + */ +add_task(function* test_same_origin_metarefresh_different_origin() { + yield doTest(gHttpTestRoot2 + "file_bug906190_1.html", + gHttpTestRoot2 + "file_bug906190_3_4.html", function* () { + // The doorhanger should appear and activeBlocked should be >> TRUE <<. + yield assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, activeBlocked: true, passiveLoaded: false, + }); + + is(content.document.getElementById('mctestdiv').innerHTML, + "Mixed Content Blocker enabled", "OK: Blocked mixed script"); + }, true); +}); + +/** + * 5. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from the same origin using 302 redirect + */ +add_task(function* test_same_origin_302redirect_same_origin() { + // the sjs files returns a 302 redirect- note, same origins + yield doTest(gHttpTestRoot1 + "file_bug906190_1.html", + gHttpTestRoot1 + "file_bug906190.sjs", function* () { + // The doorhanger should appear but activeBlocked should be >> NOT << true. + // Currently it is >> TRUE << - see follow up bug 914860 + ok(!gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"), + "OK: Mixed Content is NOT being blocked"); + + is(content.document.getElementById('mctestdiv').innerHTML, + "Mixed Content Blocker disabled", "OK: Executed mixed script"); + }); +}); + +/** + * 6. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from a different origin using 302 redirect + */ +add_task(function* test_same_origin_302redirect_different_origin() { + // the sjs files returns a 302 redirect - note, different origins + yield doTest(gHttpTestRoot2 + "file_bug906190_1.html", + gHttpTestRoot2 + "file_bug906190.sjs", function* () { + // The doorhanger should appear and activeBlocked should be >> TRUE <<. + yield assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, activeBlocked: true, passiveLoaded: false, + }); + + is(content.document.getElementById('mctestdiv').innerHTML, + "Mixed Content Blocker enabled", "OK: Blocked mixed script"); + }); +}); + +/** + * 7. - Test memory leak issue on redirection error. See Bug 1269426. + */ +add_task(function* test_bad_redirection() { + // the sjs files returns a 302 redirect - note, different origins + yield doTest(gHttpTestRoot2 + "file_bug906190_1.html", + gHttpTestRoot2 + "file_bug906190.sjs?bad-redirection=1", function* () { + // Nothing to do. Just see if memory leak is reported in the end. + ok(true, "Nothing to do"); + }); +}); diff --git a/browser/base/content/test/general/browser_bug963945.js b/browser/base/content/test/general/browser_bug963945.js new file mode 100644 index 000000000..4531964b0 --- /dev/null +++ b/browser/base/content/test/general/browser_bug963945.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This test ensures the about:addons tab is only + * opened one time when in private browsing. + */ + +add_task(function* test() { + let win = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + + let tab = win.gBrowser.selectedTab = win.gBrowser.addTab("about:addons"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield promiseWaitForFocus(win); + + EventUtils.synthesizeKey("a", { ctrlKey: true, shiftKey: true }, win); + + is(win.gBrowser.tabs.length, 2, "about:addons tab was re-focused."); + is(win.gBrowser.currentURI.spec, "about:addons", "Addons tab was opened."); + + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/base/content/test/general/browser_bug970746.js b/browser/base/content/test/general/browser_bug970746.js new file mode 100644 index 000000000..623623e55 --- /dev/null +++ b/browser/base/content/test/general/browser_bug970746.js @@ -0,0 +1,121 @@ +/* Make sure context menu includes option to search hyperlink text on search engine */ + +add_task(function *() { + const url = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug970746.xhtml"; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + const ellipsis = "\u2026"; + + let contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + + // Tests if the "Search <engine> for '<some terms>'" context menu item is shown for the + // given query string of an element. Tests to make sure label includes the proper search terms. + // + // Each test: + // + // id: The id of the element to test. + // isSelected: Flag to enable selecting (text highlight) the contents of the element + // shouldBeShown: The display state of the menu item + // expectedLabelContents: The menu item label should contain a portion of this string. + // Will only be tested if shouldBeShown is true. + let tests = [ + { + id: "link", + isSelected: true, + shouldBeShown: true, + expectedLabelContents: "I'm a link!", + }, + { + id: "link", + isSelected: false, + shouldBeShown: true, + expectedLabelContents: "I'm a link!", + }, + { + id: "longLink", + isSelected: true, + shouldBeShown: true, + expectedLabelContents: "I'm a really lo" + ellipsis, + }, + { + id: "longLink", + isSelected: false, + shouldBeShown: true, + expectedLabelContents: "I'm a really lo" + ellipsis, + }, + { + id: "plainText", + isSelected: true, + shouldBeShown: true, + expectedLabelContents: "Right clicking " + ellipsis, + }, + { + id: "plainText", + isSelected: false, + shouldBeShown: false, + }, + { + id: "mixedContent", + isSelected: true, + shouldBeShown: true, + expectedLabelContents: "I'm some text, " + ellipsis, + }, + { + id: "mixedContent", + isSelected: false, + shouldBeShown: false, + }, + { + id: "partialLink", + isSelected: true, + shouldBeShown: true, + expectedLabelContents: "link selection", + }, + { + id: "partialLink", + isSelected: false, + shouldBeShown: true, + expectedLabelContents: "A partial link " + ellipsis, + }, + { + id: "surrogatePair", + isSelected: true, + shouldBeShown: true, + expectedLabelContents: "This character\uD83D\uDD25" + ellipsis, + } + ]; + + for (let test of tests) { + yield ContentTask.spawn(gBrowser.selectedBrowser, + { selectElement: test.isSelected ? test.id : null }, + function* (arg) { + let selection = content.getSelection(); + selection.removeAllRanges(); + + if (arg.selectElement) { + selection.selectAllChildren(content.document.getElementById(arg.selectElement)); + } + }); + + let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouseAtCenter("#" + test.id, + { type: "contextmenu", button: 2}, gBrowser.selectedBrowser); + yield popupShownPromise; + + let menuItem = document.getElementById("context-searchselect"); + is(menuItem.hidden, !test.shouldBeShown, + "search context menu item is shown for '#" + test.id + "' and selected is '" + test.isSelected + "'"); + + if (test.shouldBeShown) { + ok(menuItem.label.includes(test.expectedLabelContents), + "Menu item text '" + menuItem.label + "' contains the correct search terms '" + test.expectedLabelContents + "'"); + } + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden"); + contentAreaContextMenu.hidePopup(); + yield popupHiddenPromise; + } + + // cleanup + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_bug970746.xhtml b/browser/base/content/test/general/browser_bug970746.xhtml new file mode 100644 index 000000000..9d78d7147 --- /dev/null +++ b/browser/base/content/test/general/browser_bug970746.xhtml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<html xmlns="http://www.w3.org/1999/xhtml"> + <body> + <a href="http://mozilla.org" id="link">I'm a link!</a> + <a href="http://mozilla.org" id="longLink">I'm a really long link and I should be truncated.</a> + + <span id="plainText"> + Right clicking me when I'm selected should show the menu item. + </span> + <span id="mixedContent"> + I'm some text, and <a href="http://mozilla.org">I'm a link!</a> + </span> + + <a href="http://mozilla.org">A partial <span id="partialLink">link selection</span></a> + + <span id="surrogatePair"> + This character🔥 shouldn't be truncated. + </span> + </body> +</html> diff --git a/browser/base/content/test/general/browser_clipboard.js b/browser/base/content/test/general/browser_clipboard.js new file mode 100644 index 000000000..33c6de52d --- /dev/null +++ b/browser/base/content/test/general/browser_clipboard.js @@ -0,0 +1,174 @@ +// This test is used to check copy and paste in editable areas to ensure that non-text +// types (html and images) are copied to and pasted from the clipboard properly. + +var testPage = "<body style='margin: 0'>" + + " <img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" + + " <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" + + "</body>"; + +add_task(function*() { + let tab = gBrowser.addTab(); + let browser = gBrowser.getBrowserForTab(tab); + + gBrowser.selectedTab = tab; + + yield promiseTabLoadEvent(tab, "data:text/html," + escape(testPage)); + yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW); + + const modifier = (navigator.platform.indexOf("Mac") >= 0) ? + Components.interfaces.nsIDOMWindowUtils.MODIFIER_META : + Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL; + + // On windows, HTML clipboard includes extra data. + // The values are from widget/windows/nsDataObj.cpp. + const htmlPrefix = (navigator.platform.indexOf("Win") >= 0) ? "<html><body>\n<!--StartFragment-->" : ""; + const htmlPostfix = (navigator.platform.indexOf("Win") >= 0) ? "<!--EndFragment-->\n</body>\n</html>" : ""; + + yield ContentTask.spawn(browser, { modifier, htmlPrefix, htmlPostfix }, function* (arg) { + var doc = content.document; + var main = doc.getElementById("main"); + main.focus(); + + const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + + function sendKey(key) { + if (utils.sendKeyEvent("keydown", key, 0, arg.modifier)) { + utils.sendKeyEvent("keypress", key, key.charCodeAt(0), arg.modifier); + } + utils.sendKeyEvent("keyup", key, 0, arg.modifier); + } + + // Select an area of the text. + let selection = doc.getSelection(); + selection.modify("move", "left", "line"); + selection.modify("move", "right", "character"); + selection.modify("move", "right", "character"); + selection.modify("move", "right", "character"); + selection.modify("extend", "right", "word"); + selection.modify("extend", "right", "word"); + + yield new Promise((resolve, reject) => { + addEventListener("copy", function copyEvent(event) { + removeEventListener("copy", copyEvent, true); + // The data is empty as the selection is copied during the event default phase. + Assert.equal(event.clipboardData.mozItemCount, 0, "Zero items on clipboard"); + resolve(); + }, true) + + sendKey("c"); + }); + + selection.modify("move", "right", "line"); + + yield new Promise((resolve, reject) => { + addEventListener("paste", function copyEvent(event) { + removeEventListener("paste", copyEvent, true); + let clipboardData = event.clipboardData; + Assert.equal(clipboardData.mozItemCount, 1, "One item on clipboard"); + Assert.equal(clipboardData.types.length, 2, "Two types on clipboard"); + Assert.equal(clipboardData.types[0], "text/html", "text/html on clipboard"); + Assert.equal(clipboardData.types[1], "text/plain", "text/plain on clipboard"); + Assert.equal(clipboardData.getData("text/html"), arg.htmlPrefix + + "t <b>Bold</b>" + arg.htmlPostfix, "text/html value"); + Assert.equal(clipboardData.getData("text/plain"), "t Bold", "text/plain value"); + resolve(); + }, true) + sendKey("v"); + }); + + Assert.equal(main.innerHTML, "Test <b>Bold</b> After Textt <b>Bold</b>", "Copy and paste html"); + + selection.modify("extend", "left", "word"); + selection.modify("extend", "left", "word"); + selection.modify("extend", "left", "character"); + + yield new Promise((resolve, reject) => { + addEventListener("cut", function copyEvent(event) { + removeEventListener("cut", copyEvent, true); + event.clipboardData.setData("text/plain", "Some text"); + event.clipboardData.setData("text/html", "<i>Italic</i> "); + selection.deleteFromDocument(); + event.preventDefault(); + resolve(); + }, true) + sendKey("x"); + }); + + selection.modify("move", "left", "line"); + + yield new Promise((resolve, reject) => { + addEventListener("paste", function copyEvent(event) { + removeEventListener("paste", copyEvent, true); + let clipboardData = event.clipboardData; + Assert.equal(clipboardData.mozItemCount, 1, "One item on clipboard 2"); + Assert.equal(clipboardData.types.length, 2, "Two types on clipboard 2"); + Assert.equal(clipboardData.types[0], "text/html", "text/html on clipboard 2"); + Assert.equal(clipboardData.types[1], "text/plain", "text/plain on clipboard 2"); + Assert.equal(clipboardData.getData("text/html"), arg.htmlPrefix + + "<i>Italic</i> " + arg.htmlPostfix, "text/html value 2"); + Assert.equal(clipboardData.getData("text/plain"), "Some text", "text/plain value 2"); + resolve(); + }, true) + sendKey("v"); + }); + + Assert.equal(main.innerHTML, "<i>Italic</i> Test <b>Bold</b> After<b></b>", + "Copy and paste html 2"); + }); + + // Next, check that the Copy Image command works. + + // The context menu needs to be opened to properly initialize for the copy + // image command to run. + let contextMenu = document.getElementById("contentAreaContextMenu"); + let contextMenuShown = promisePopupShown(contextMenu); + BrowserTestUtils.synthesizeMouseAtCenter("#img", { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser); + yield contextMenuShown; + + document.getElementById("context-copyimage-contents").doCommand(); + + contextMenu.hidePopup(); + yield promisePopupHidden(contextMenu); + + // Focus the content again + yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW); + + yield ContentTask.spawn(browser, { modifier, htmlPrefix, htmlPostfix }, function* (arg) { + var doc = content.document; + var main = doc.getElementById("main"); + main.focus(); + + yield new Promise((resolve, reject) => { + addEventListener("paste", function copyEvent(event) { + removeEventListener("paste", copyEvent, true); + let clipboardData = event.clipboardData; + + // DataTransfer doesn't support the image types yet, so only text/html + // will be present. + if (clipboardData.getData("text/html") !== arg.htmlPrefix + + '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' + + arg.htmlPostfix) { + reject('Clipboard Data did not contain an image, was ' + clipboardData.getData("text/html")); + } + resolve(); + }, true) + + const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + + if (utils.sendKeyEvent("keydown", "v", 0, arg.modifier)) { + utils.sendKeyEvent("keypress", "v", "v".charCodeAt(0), arg.modifier); + } + utils.sendKeyEvent("keyup", "v", 0, arg.modifier); + }); + + // The new content should now include an image. + Assert.equal(main.innerHTML, '<i>Italic</i> <img id="img" tabindex="1" ' + + 'src="http://example.org/browser/browser/base/content/test/general/moz.png">' + + 'Test <b>Bold</b> After<b></b>', "Paste after copy image"); + }); + + gBrowser.removeCurrentTab(); +}); + diff --git a/browser/base/content/test/general/browser_clipboard_pastefile.js b/browser/base/content/test/general/browser_clipboard_pastefile.js new file mode 100644 index 000000000..fe87284f3 --- /dev/null +++ b/browser/base/content/test/general/browser_clipboard_pastefile.js @@ -0,0 +1,62 @@ +// This test is used to check that pasting files removes all non-file data from +// event.clipboardData. + +add_task(function*() { + var textbox = document.createElement("textbox"); + document.documentElement.appendChild(textbox); + + textbox.focus(); + textbox.value = "Text"; + textbox.select(); + + yield new Promise((resolve, reject) => { + textbox.addEventListener("copy", function copyEvent(event) { + textbox.removeEventListener("copy", copyEvent, true); + event.clipboardData.setData("text/plain", "Alternate"); + // For this test, it doesn't matter that the file isn't actually a file. + event.clipboardData.setData("application/x-moz-file", "Sample"); + event.preventDefault(); + resolve(); + }, true) + + EventUtils.synthesizeKey("c", { accelKey: true }); + }); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, + "https://example.com/browser/browser/base/content/test/general/clipboard_pastefile.html"); + let browser = tab.linkedBrowser; + + yield ContentTask.spawn(browser, { }, function* (arg) { + content.document.getElementById("input").focus(); + }); + + yield BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser); + + let output = yield ContentTask.spawn(browser, { }, function* (arg) { + return content.document.getElementById("output").textContent; + }); + is (output, "Passed", "Paste file"); + + textbox.focus(); + + yield new Promise((resolve, reject) => { + textbox.addEventListener("paste", function copyEvent(event) { + textbox.removeEventListener("paste", copyEvent, true); + + let dt = event.clipboardData; + is(dt.types.length, 3, "number of types"); + ok(dt.types.includes("text/plain"), "text/plain exists in types"); + ok(dt.mozTypesAt(0).contains("text/plain"), "text/plain exists in mozTypesAt"); + is(dt.getData("text/plain"), "Alternate", "text/plain returned in getData"); + is(dt.mozGetDataAt("text/plain", 0), "Alternate", "text/plain returned in mozGetDataAt"); + + resolve(); + }, true); + + EventUtils.synthesizeKey("v", { accelKey: true }); + }); + + document.documentElement.removeChild(textbox); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_contentAltClick.js b/browser/base/content/test/general/browser_contentAltClick.js new file mode 100644 index 000000000..1a3b0fccc --- /dev/null +++ b/browser/base/content/test/general/browser_contentAltClick.js @@ -0,0 +1,107 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test for Bug 1109146. + * The tests opens a new tab and alt + clicks to download files + * and confirms those files are on the download list. + * + * The difference between this and the test "browser_contentAreaClick.js" is that + * the code path in e10s uses ContentClick.jsm instead of browser.js::contentAreaClick() util. + */ +"use strict"; + +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm"); + +function setup() { + gPrefService.setBoolPref("browser.altClickSave", true); + + let testPage = + 'data:text/html,' + + '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' + + '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' + + '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>'; + + return BrowserTestUtils.openNewForegroundTab(gBrowser, testPage); +} + +function* clean_up() { + // Remove downloads. + let downloadList = yield Downloads.getList(Downloads.ALL); + let downloads = yield downloadList.getAll(); + for (let download of downloads) { + yield downloadList.remove(download); + yield download.finalize(true); + } + // Remove download history. + yield PlacesTestUtils.clearHistory(); + + gPrefService.clearUserPref("browser.altClickSave"); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +} + +add_task(function* test_alt_click() +{ + yield setup(); + + let downloadList = yield Downloads.getList(Downloads.ALL); + let downloads = []; + let downloadView; + // When 1 download has been attempted then resolve the promise. + let finishedAllDownloads = new Promise( (resolve) => { + downloadView = { + onDownloadAdded: function (aDownload) { + downloads.push(aDownload); + resolve(); + }, + }; + }); + yield downloadList.addView(downloadView); + yield BrowserTestUtils.synthesizeMouseAtCenter("#commonlink", {altKey: true}, gBrowser.selectedBrowser); + + // Wait for all downloads to be added to the download list. + yield finishedAllDownloads; + yield downloadList.removeView(downloadView); + + is(downloads.length, 1, "1 downloads"); + is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #commonlink element"); + + yield* clean_up(); +}); + +add_task(function* test_alt_click_on_xlinks() +{ + yield setup(); + + let downloadList = yield Downloads.getList(Downloads.ALL); + let downloads = []; + let downloadView; + // When all 2 downloads have been attempted then resolve the promise. + let finishedAllDownloads = new Promise( (resolve) => { + downloadView = { + onDownloadAdded: function (aDownload) { + downloads.push(aDownload); + if (downloads.length == 2) { + resolve(); + } + }, + }; + }); + yield downloadList.addView(downloadView); + yield BrowserTestUtils.synthesizeMouseAtCenter("#mathxlink", {altKey: true}, gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeMouseAtCenter("#svgxlink", {altKey: true}, gBrowser.selectedBrowser); + + // Wait for all downloads to be added to the download list. + yield finishedAllDownloads; + yield downloadList.removeView(downloadView); + + is(downloads.length, 2, "2 downloads"); + is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #mathxlink element"); + is(downloads[1].source.url, "http://mochi.test/moz/", "Downloaded #svgxlink element"); + + yield* clean_up(); +}); diff --git a/browser/base/content/test/general/browser_contentAreaClick.js b/browser/base/content/test/general/browser_contentAreaClick.js new file mode 100644 index 000000000..facdfb498 --- /dev/null +++ b/browser/base/content/test/general/browser_contentAreaClick.js @@ -0,0 +1,307 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test for bug 549340. + * Test for browser.js::contentAreaClick() util. + * + * The test opens a new browser window, then replaces browser.js methods invoked + * by contentAreaClick with a mock function that tracks which methods have been + * called. + * Each sub-test synthesizes a mouse click event on links injected in content, + * the event is collected by a click handler that ensures that contentAreaClick + * correctly prevent default events, and follows the correct code path. + */ + +var gTests = [ + + { + desc: "Simple left click", + setup: function() {}, + clean: function() {}, + event: {}, + targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ], + expectedInvokedMethods: [], + preventDefault: false, + }, + + { + desc: "Ctrl/Cmd left click", + setup: function() {}, + clean: function() {}, + event: { ctrlKey: true, + metaKey: true }, + targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ], + expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ], + preventDefault: true, + }, + + // The next test was once handling feedService.forcePreview(). Now it should + // just be like Alt click. + { + desc: "Shift+Alt left click", + setup: function() { + gPrefService.setBoolPref("browser.altClickSave", true); + }, + clean: function() { + gPrefService.clearUserPref("browser.altClickSave"); + }, + event: { shiftKey: true, + altKey: true }, + targets: [ "commonlink", "maplink" ], + expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ], + preventDefault: true, + }, + + { + desc: "Shift+Alt left click on XLinks", + setup: function() { + gPrefService.setBoolPref("browser.altClickSave", true); + }, + clean: function() { + gPrefService.clearUserPref("browser.altClickSave"); + }, + event: { shiftKey: true, + altKey: true }, + targets: [ "mathxlink", "svgxlink"], + expectedInvokedMethods: [ "saveURL" ], + preventDefault: true, + }, + + { + desc: "Shift click", + setup: function() {}, + clean: function() {}, + event: { shiftKey: true }, + targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ], + expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ], + preventDefault: true, + }, + + { + desc: "Alt click", + setup: function() { + gPrefService.setBoolPref("browser.altClickSave", true); + }, + clean: function() { + gPrefService.clearUserPref("browser.altClickSave"); + }, + event: { altKey: true }, + targets: [ "commonlink", "maplink" ], + expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ], + preventDefault: true, + }, + + { + desc: "Alt click on XLinks", + setup: function() { + gPrefService.setBoolPref("browser.altClickSave", true); + }, + clean: function() { + gPrefService.clearUserPref("browser.altClickSave"); + }, + event: { altKey: true }, + targets: [ "mathxlink", "svgxlink" ], + expectedInvokedMethods: [ "saveURL" ], + preventDefault: true, + }, + + { + desc: "Panel click", + setup: function() {}, + clean: function() {}, + event: {}, + targets: [ "panellink" ], + expectedInvokedMethods: [ "urlSecurityCheck", "loadURI" ], + preventDefault: true, + }, + + { + desc: "Simple middle click opentab", + setup: function() {}, + clean: function() {}, + event: { button: 1 }, + targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ], + expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ], + preventDefault: true, + }, + + { + desc: "Simple middle click openwin", + setup: function() { + gPrefService.setBoolPref("browser.tabs.opentabfor.middleclick", false); + }, + clean: function() { + gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick"); + }, + event: { button: 1 }, + targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ], + expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ], + preventDefault: true, + }, + + { + desc: "Middle mouse paste", + setup: function() { + gPrefService.setBoolPref("middlemouse.contentLoadURL", true); + gPrefService.setBoolPref("general.autoScroll", false); + }, + clean: function() { + gPrefService.clearUserPref("middlemouse.contentLoadURL"); + gPrefService.clearUserPref("general.autoScroll"); + }, + event: { button: 1 }, + targets: [ "emptylink" ], + expectedInvokedMethods: [ "middleMousePaste" ], + preventDefault: true, + }, + +]; + +// Array of method names that will be replaced in the new window. +var gReplacedMethods = [ + "middleMousePaste", + "urlSecurityCheck", + "loadURI", + "gatherTextUnder", + "saveURL", + "openLinkIn", + "getShortcutOrURIAndPostData", +]; + +// Reference to the new window. +var gTestWin = null; + +// List of methods invoked by a specific call to contentAreaClick. +var gInvokedMethods = []; + +// The test currently running. +var gCurrentTest = null; + +function test() { + waitForExplicitFinish(); + + gTestWin = openDialog(location, "", "chrome,all,dialog=no", "about:blank"); + whenDelayedStartupFinished(gTestWin, function () { + info("Browser window opened"); + waitForFocus(function() { + info("Browser window focused"); + waitForFocus(function() { + info("Setting up browser..."); + setupTestBrowserWindow(); + info("Running tests..."); + executeSoon(runNextTest); + }, gTestWin.content, true); + }, gTestWin); + }); +} + +// Click handler used to steal click events. +var gClickHandler = { + handleEvent: function (event) { + let linkId = event.target.id || event.target.localName; + is(event.type, "click", + gCurrentTest.desc + ":Handler received a click event on " + linkId); + + let isPanelClick = linkId == "panellink"; + gTestWin.contentAreaClick(event, isPanelClick); + let prevent = event.defaultPrevented; + is(prevent, gCurrentTest.preventDefault, + gCurrentTest.desc + ": event.defaultPrevented is correct (" + prevent + ")") + + // Check that all required methods have been called. + gCurrentTest.expectedInvokedMethods.forEach(function(aExpectedMethodName) { + isnot(gInvokedMethods.indexOf(aExpectedMethodName), -1, + gCurrentTest.desc + ":" + aExpectedMethodName + " was invoked"); + }); + + if (gInvokedMethods.length != gCurrentTest.expectedInvokedMethods.length) { + ok(false, "Wrong number of invoked methods"); + gInvokedMethods.forEach(method => info(method + " was invoked")); + } + + event.preventDefault(); + event.stopPropagation(); + + executeSoon(runNextTest); + } +} + +// Wraps around the methods' replacement mock function. +function wrapperMethod(aInvokedMethods, aMethodName) { + return function () { + aInvokedMethods.push(aMethodName); + // At least getShortcutOrURIAndPostData requires to return url + return (aMethodName == "getShortcutOrURIAndPostData") ? arguments.url : arguments[0]; + } +} + +function setupTestBrowserWindow() { + // Steal click events and don't propagate them. + gTestWin.addEventListener("click", gClickHandler, true); + + // Replace methods. + gReplacedMethods.forEach(function (aMethodName) { + gTestWin["old_" + aMethodName] = gTestWin[aMethodName]; + gTestWin[aMethodName] = wrapperMethod(gInvokedMethods, aMethodName); + }); + + // Inject links in content. + let doc = gTestWin.content.document; + let mainDiv = doc.createElement("div"); + mainDiv.innerHTML = + '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' + + '<p><a id="panellink" href="http://mochi.test/moz/">Panel link</a></p>' + + '<p><a id="emptylink">Empty link</a></p>' + + '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' + + '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>' + + '<p><map name="map" id="map"><area href="http://mochi.test/moz/" shape="rect" coords="0,0,128,128" /></map><img id="maplink" usemap="#map" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAABGdBTUEAALGPC%2FxhBQAAAOtJREFUeF7t0IEAAAAAgKD9qRcphAoDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGBgwIAAAT0N51AAAAAASUVORK5CYII%3D"/></p>' + doc.body.appendChild(mainDiv); +} + +function runNextTest() { + if (!gCurrentTest) { + gCurrentTest = gTests.shift(); + gCurrentTest.setup(); + } + + if (gCurrentTest.targets.length == 0) { + info(gCurrentTest.desc + ": cleaning up...") + gCurrentTest.clean(); + + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + gCurrentTest.setup(); + } + else { + finishTest(); + return; + } + } + + // Move to next target. + gInvokedMethods.length = 0; + let target = gCurrentTest.targets.shift(); + + info(gCurrentTest.desc + ": testing " + target); + + // Fire click event. + let targetElt = gTestWin.content.document.getElementById(target); + ok(targetElt, gCurrentTest.desc + ": target is valid (" + targetElt.id + ")"); + EventUtils.synthesizeMouseAtCenter(targetElt, gCurrentTest.event, gTestWin.content); +} + +function finishTest() { + info("Restoring browser..."); + gTestWin.removeEventListener("click", gClickHandler, true); + + // Restore original methods. + gReplacedMethods.forEach(function (aMethodName) { + gTestWin[aMethodName] = gTestWin["old_" + aMethodName]; + delete gTestWin["old_" + aMethodName]; + }); + + gTestWin.close(); + finish(); +} diff --git a/browser/base/content/test/general/browser_contentSearchUI.js b/browser/base/content/test/general/browser_contentSearchUI.js new file mode 100644 index 000000000..003f80aff --- /dev/null +++ b/browser/base/content/test/general/browser_contentSearchUI.js @@ -0,0 +1,771 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_PAGE_BASENAME = "contentSearchUI.html"; +const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js"; +const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine"; +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; +const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml"; + +const TEST_MSG = "ContentSearchUIControllerTest"; + +requestLongerTimeout(2); + +add_task(function* emptyInput() { + yield setUp(); + + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("key", "VK_BACK_SPACE"); + checkState(state, "", [], -1); + + yield msg("reset"); +}); + +add_task(function* blur() { + yield setUp(); + + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("blur"); + checkState(state, "x", [], -1); + + yield msg("reset"); +}); + +add_task(function* upDownKeys() { + yield setUp(); + + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + // Cycle down the suggestions starting from no selection. + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbar", ["xfoo", "xbar"], 1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "x", ["xfoo", "xbar"], 2); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "x", ["xfoo", "xbar"], 3); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "x", ["xfoo", "xbar"], -1); + + // Cycle up starting from no selection. + state = yield msg("key", "VK_UP"); + checkState(state, "x", ["xfoo", "xbar"], 3); + + state = yield msg("key", "VK_UP"); + checkState(state, "x", ["xfoo", "xbar"], 2); + + state = yield msg("key", "VK_UP"); + checkState(state, "xbar", ["xfoo", "xbar"], 1); + + state = yield msg("key", "VK_UP"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0); + + state = yield msg("key", "VK_UP"); + checkState(state, "x", ["xfoo", "xbar"], -1); + + yield msg("reset"); +}); + +add_task(function* rightLeftKeys() { + yield setUp(); + + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("key", "VK_LEFT"); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("key", "VK_LEFT"); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("key", "VK_RIGHT"); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("key", "VK_RIGHT"); + checkState(state, "x", [], -1); + + state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0); + + // This should make the xfoo suggestion sticky. To make sure it sticks, + // trigger suggestions again and cycle through them by pressing Down until + // nothing is selected again. + state = yield msg("key", "VK_RIGHT"); + checkState(state, "xfoo", [], -1); + + state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1); + + yield msg("reset"); +}); + +add_task(function* tabKey() { + yield setUp(); + yield msg("key", { key: "x", waitForSuggestions: true }); + + let state = yield msg("key", "VK_TAB"); + checkState(state, "x", ["xfoo", "xbar"], 2); + + state = yield msg("key", "VK_TAB"); + checkState(state, "x", ["xfoo", "xbar"], 3); + + state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }}); + checkState(state, "x", ["xfoo", "xbar"], 2); + + state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }}); + checkState(state, "x", [], -1); + + yield setUp(); + + yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + + for (let i = 0; i < 3; ++i) { + state = yield msg("key", "VK_TAB"); + } + checkState(state, "x", [], -1); + + yield setUp(); + + yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0); + + state = yield msg("key", "VK_TAB"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0, 0); + + state = yield msg("key", "VK_TAB"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0, 1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "x", ["xfoo", "xbar"], 2); + + state = yield msg("key", "VK_UP"); + checkState(state, "xbar", ["xfoo", "xbar"], 1); + + state = yield msg("key", "VK_TAB"); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 0); + + state = yield msg("key", "VK_TAB"); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 1); + + state = yield msg("key", "VK_TAB"); + checkState(state, "xbar", [], -1); + + yield msg("reset"); +}); + +add_task(function* cycleSuggestions() { + yield setUp(); + yield msg("key", { key: "x", waitForSuggestions: true }); + + let cycle = Task.async(function* (aSelectedButtonIndex) { + let modifiers = { + shiftKey: true, + accelKey: true, + }; + + let state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex); + }); + + yield cycle(); + + // Repeat with a one-off selected. + let state = yield msg("key", "VK_TAB"); + checkState(state, "x", ["xfoo", "xbar"], 2); + yield cycle(0); + + // Repeat with the settings button selected. + state = yield msg("key", "VK_TAB"); + checkState(state, "x", ["xfoo", "xbar"], 3); + yield cycle(1); + + yield msg("reset"); +}); + +add_task(function* cycleOneOffs() { + yield setUp(); + yield msg("key", { key: "x", waitForSuggestions: true }); + + yield msg("addDuplicateOneOff"); + + let state = yield msg("key", "VK_DOWN"); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbar", ["xfoo", "xbar"], 1); + + let modifiers = { + altKey: true, + }; + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 0); + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 1); + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 1); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 0); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1); + + // If the settings button is selected, pressing alt+up/down should select the + // last/first one-off respectively (and deselect the settings button). + yield msg("key", "VK_TAB"); + yield msg("key", "VK_TAB"); + state = yield msg("key", "VK_TAB"); // Settings button selected. + checkState(state, "xbar", ["xfoo", "xbar"], 1, 2); + + state = yield msg("key", { key: "VK_UP", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 1); + + state = yield msg("key", "VK_TAB"); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 2); + + state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers }); + checkState(state, "xbar", ["xfoo", "xbar"], 1, 0); + + yield msg("removeLastOneOff"); + yield msg("reset"); +}); + +add_task(function* mouse() { + yield setUp(); + + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("mousemove", 0); + checkState(state, "x", ["xfoo", "xbar"], 0); + + state = yield msg("mousemove", 1); + checkState(state, "x", ["xfoo", "xbar"], 1); + + state = yield msg("mousemove", 2); + checkState(state, "x", ["xfoo", "xbar"], 1, 0); + + state = yield msg("mousemove", 3); + checkState(state, "x", ["xfoo", "xbar"], 1, 1); + + state = yield msg("mousemove", -1); + checkState(state, "x", ["xfoo", "xbar"], 1); + + yield msg("reset"); + yield setUp(); + + state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + state = yield msg("mousemove", 0); + checkState(state, "x", ["xfoo", "xbar"], 0); + + state = yield msg("mousemove", 2); + checkState(state, "x", ["xfoo", "xbar"], 0, 0); + + state = yield msg("mousemove", -1); + checkState(state, "x", ["xfoo", "xbar"], 0); + + yield msg("reset"); +}); + +add_task(function* formHistory() { + yield setUp(); + + // Type an X and add it to form history. + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + // Wait for Satchel to say it's been added to form history. + let deferred = Promise.defer(); + Services.obs.addObserver(function onAdd(subj, topic, data) { + if (data == "formhistory-add") { + Services.obs.removeObserver(onAdd, "satchel-storage-changed"); + executeSoon(() => deferred.resolve()); + } + }, "satchel-storage-changed", false); + yield Promise.all([msg("addInputValueToFormHistory"), deferred.promise]); + + // Reset the input. + state = yield msg("reset"); + checkState(state, "", [], -1); + + // Type an X again. The form history entry should appear. + state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"], + -1); + + // Select the form history entry and delete it. + state = yield msg("key", "VK_DOWN"); + checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"], + 0); + + // Wait for Satchel. + deferred = Promise.defer(); + Services.obs.addObserver(function onRemove(subj, topic, data) { + if (data == "formhistory-remove") { + Services.obs.removeObserver(onRemove, "satchel-storage-changed"); + executeSoon(() => deferred.resolve()); + } + }, "satchel-storage-changed", false); + + state = yield msg("key", "VK_DELETE"); + checkState(state, "x", ["xfoo", "xbar"], -1); + + yield deferred.promise; + + // Reset the input. + state = yield msg("reset"); + checkState(state, "", [], -1); + + // Type an X again. The form history entry should still be gone. + state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + yield msg("reset"); +}); + +add_task(function* cycleEngines() { + yield setUp(); + yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + + let promiseEngineChange = function(newEngineName) { + let deferred = Promise.defer(); + Services.obs.addObserver(function resolver(subj, topic, data) { + if (data != "engine-current") { + return; + } + SimpleTest.is(subj.name, newEngineName, "Engine cycled correctly"); + Services.obs.removeObserver(resolver, "browser-search-engine-modified"); + deferred.resolve(); + }, "browser-search-engine-modified", false); + return deferred.promise; + } + + let p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME); + yield msg("key", { key: "VK_DOWN", modifiers: { accelKey: true }}); + yield p; + + p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME); + yield msg("key", { key: "VK_UP", modifiers: { accelKey: true }}); + yield p; + + yield msg("reset"); +}); + +add_task(function* search() { + yield setUp(); + + let modifiers = {}; + ["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true); + + // Test typing a query and pressing enter. + let p = msg("waitForSearch"); + yield msg("key", { key: "x", waitForSuggestions: true }); + yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); + let mesg = yield p; + let eventData = { + engineName: TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME, + searchString: "x", + healthReportKey: "test", + searchPurpose: "test", + originalEvent: modifiers, + }; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test typing a query, then selecting a suggestion and pressing enter. + p = msg("waitForSearch"); + yield msg("key", { key: "x", waitForSuggestions: true }); + yield msg("key", "VK_DOWN"); + yield msg("key", "VK_DOWN"); + yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); + mesg = yield p; + eventData.searchString = "xfoo"; + eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME; + eventData.selection = { + index: 1, + kind: "key", + } + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test typing a query, then selecting a one-off button and pressing enter. + p = msg("waitForSearch"); + yield msg("key", { key: "x", waitForSuggestions: true }); + yield msg("key", "VK_UP"); + yield msg("key", "VK_UP"); + yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); + mesg = yield p; + delete eventData.selection; + eventData.searchString = "x"; + eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test typing a query and clicking the search engine header. + p = msg("waitForSearch"); + modifiers.button = 0; + yield msg("key", { key: "x", waitForSuggestions: true }); + yield msg("mousemove", -1); + yield msg("click", { eltIdx: -1, modifiers: modifiers }); + mesg = yield p; + eventData.originalEvent = modifiers; + eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test typing a query and then clicking a suggestion. + yield msg("key", { key: "x", waitForSuggestions: true }); + p = msg("waitForSearch"); + yield msg("mousemove", 1); + yield msg("click", { eltIdx: 1, modifiers: modifiers }); + mesg = yield p; + eventData.searchString = "xfoo"; + eventData.selection = { + index: 1, + kind: "mouse", + }; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test typing a query and then clicking a one-off button. + yield msg("key", { key: "x", waitForSuggestions: true }); + p = msg("waitForSearch"); + yield msg("mousemove", 3); + yield msg("click", { eltIdx: 3, modifiers: modifiers }); + mesg = yield p; + eventData.searchString = "x"; + eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME; + delete eventData.selection; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test selecting a suggestion, then clicking a one-off without deselecting the + // suggestion. + yield msg("key", { key: "x", waitForSuggestions: true }); + p = msg("waitForSearch"); + yield msg("mousemove", 1); + yield msg("mousemove", 3); + yield msg("click", { eltIdx: 3, modifiers: modifiers }); + mesg = yield p; + eventData.searchString = "xfoo" + eventData.selection = { + index: 1, + kind: "mouse", + }; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Same as above, but with the keyboard. + delete modifiers.button; + yield msg("key", { key: "x", waitForSuggestions: true }); + p = msg("waitForSearch"); + yield msg("key", "VK_DOWN"); + yield msg("key", "VK_DOWN"); + yield msg("key", "VK_TAB"); + yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); + mesg = yield p; + eventData.selection = { + index: 1, + kind: "key", + }; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Test searching when using IME composition. + let state = yield msg("startComposition", { data: "" }); + checkState(state, "", [], -1); + state = yield msg("changeComposition", { data: "x", waitForSuggestions: true }); + checkState(state, "x", [{ str: "x", type: "formHistory" }, + { str: "xfoo", type: "formHistory" }, "xbar"], -1); + yield msg("commitComposition"); + delete modifiers.button; + p = msg("waitForSearch"); + yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); + mesg = yield p; + eventData.searchString = "x" + eventData.originalEvent = modifiers; + eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME; + delete eventData.selection; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + state = yield msg("startComposition", { data: "" }); + checkState(state, "", [], -1); + state = yield msg("changeComposition", { data: "x", waitForSuggestions: true }); + checkState(state, "x", [{ str: "x", type: "formHistory" }, + { str: "xfoo", type: "formHistory" }, "xbar"], -1); + + // Mouse over the first suggestion. + state = yield msg("mousemove", 0); + checkState(state, "x", [{ str: "x", type: "formHistory" }, + { str: "xfoo", type: "formHistory" }, "xbar"], 0); + + // Mouse over the second suggestion. + state = yield msg("mousemove", 1); + checkState(state, "x", [{ str: "x", type: "formHistory" }, + { str: "xfoo", type: "formHistory" }, "xbar"], 1); + + modifiers.button = 0; + p = msg("waitForSearch"); + yield msg("click", { eltIdx: 1, modifiers: modifiers }); + mesg = yield p; + eventData.searchString = "xfoo"; + eventData.originalEvent = modifiers; + eventData.selection = { + index: 1, + kind: "mouse", + }; + SimpleTest.isDeeply(eventData, mesg, "Search event data"); + + yield promiseTab(); + yield setUp(); + + // Remove form history entries. + // Wait for Satchel. + let deferred = Promise.defer(); + let historyCount = 2; + Services.obs.addObserver(function onRemove(subj, topic, data) { + if (data == "formhistory-remove") { + if (--historyCount) { + return; + } + Services.obs.removeObserver(onRemove, "satchel-storage-changed"); + executeSoon(() => deferred.resolve()); + } + }, "satchel-storage-changed", false); + + yield msg("key", { key: "x", waitForSuggestions: true }); + yield msg("key", "VK_DOWN"); + yield msg("key", "VK_DOWN"); + yield msg("key", "VK_DELETE"); + yield msg("key", "VK_DOWN"); + yield msg("key", "VK_DELETE"); + yield deferred.promise; + + yield msg("reset"); + state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); + + yield promiseTab(); + yield setUp(); + yield msg("reset"); +}); + +add_task(function* settings() { + yield setUp(); + yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + yield msg("key", "VK_UP"); + let p = msg("waitForSearchSettings"); + yield msg("key", "VK_RETURN"); + yield p; + + yield msg("reset"); +}); + +var gDidInitialSetUp = false; + +function setUp(aNoEngine) { + return Task.spawn(function* () { + if (!gDidInitialSetUp) { + Cu.import("resource:///modules/ContentSearch.jsm"); + let originalOnMessageSearch = ContentSearch._onMessageSearch; + let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines; + ContentSearch._onMessageSearch = () => {}; + ContentSearch._onMessageManageEngines = () => {}; + registerCleanupFunction(() => { + ContentSearch._onMessageSearch = originalOnMessageSearch; + ContentSearch._onMessageManageEngines = originalOnMessageManageEngines; + }); + yield setUpEngines(); + yield promiseTab(); + gDidInitialSetUp = true; + } + yield msg("focus"); + }); +} + +function msg(type, data=null) { + gMsgMan.sendAsyncMessage(TEST_MSG, { + type: type, + data: data, + }); + let deferred = Promise.defer(); + gMsgMan.addMessageListener(TEST_MSG, function onMsg(msgObj) { + if (msgObj.data.type != type) { + return; + } + gMsgMan.removeMessageListener(TEST_MSG, onMsg); + deferred.resolve(msgObj.data.data); + }); + return deferred.promise; +} + +function checkState(actualState, expectedInputVal, expectedSuggestions, + expectedSelectedIdx, expectedSelectedButtonIdx) { + expectedSuggestions = expectedSuggestions.map(sugg => { + return typeof(sugg) == "object" ? sugg : { + str: sugg, + type: "remote", + }; + }); + + if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) { + expectedSelectedIdx = expectedSuggestions.length + expectedSelectedButtonIdx; + } + + let expectedState = { + selectedIndex: expectedSelectedIdx, + numSuggestions: expectedSuggestions.length, + suggestionAtIndex: expectedSuggestions.map(s => s.str), + isFormHistorySuggestionAtIndex: + expectedSuggestions.map(s => s.type == "formHistory"), + + tableHidden: expectedSuggestions.length == 0, + + inputValue: expectedInputVal, + ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true", + }; + if (expectedSelectedButtonIdx != undefined) { + expectedState.selectedButtonIndex = expectedSelectedButtonIdx; + } + else if (expectedSelectedIdx < expectedSuggestions.length) { + expectedState.selectedButtonIndex = -1; + } + else { + expectedState.selectedButtonIndex = expectedSelectedIdx - expectedSuggestions.length; + } + + SimpleTest.isDeeply(actualState, expectedState, "State"); +} + +var gMsgMan; + +function* promiseTab() { + let deferred = Promise.defer(); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + registerCleanupFunction(() => BrowserTestUtils.removeTab(tab)); + let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME; + tab.linkedBrowser.addEventListener("load", function onLoad(event) { + tab.linkedBrowser.removeEventListener("load", onLoad, true); + gMsgMan = tab.linkedBrowser.messageManager; + gMsgMan.sendAsyncMessage("ContentSearch", { + type: "AddToWhitelist", + data: [pageURL], + }); + promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => { + let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME; + gMsgMan.loadFrameScript(jsURL, false); + deferred.resolve(msg("init")); + }); + }, true, true); + openUILinkIn(pageURL, "current"); + return deferred.promise; +} + +function promiseMsg(name, type, msgMan) { + let deferred = Promise.defer(); + info("Waiting for " + name + " message " + type + "..."); + msgMan.addMessageListener(name, function onMsg(msgObj) { + info("Received " + name + " message " + msgObj.data.type + "\n"); + if (msgObj.data.type == type) { + msgMan.removeMessageListener(name, onMsg); + deferred.resolve(msgObj); + } + }); + return deferred.promise; +} + +function setUpEngines() { + return Task.spawn(function* () { + info("Removing default search engines"); + let currentEngineName = Services.search.currentEngine.name; + let currentEngines = Services.search.getVisibleEngines(); + info("Adding test search engines"); + let engine1 = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + yield promiseNewSearchEngine(TEST_ENGINE_2_BASENAME); + Services.search.currentEngine = engine1; + for (let engine of currentEngines) { + Services.search.removeEngine(engine); + } + registerCleanupFunction(() => { + Services.search.restoreDefaultEngines(); + Services.search.currentEngine = Services.search.getEngineByName(currentEngineName); + }); + }); +} diff --git a/browser/base/content/test/general/browser_contextmenu.js b/browser/base/content/test/general/browser_contextmenu.js new file mode 100644 index 000000000..3e0135848 --- /dev/null +++ b/browser/base/content/test/general/browser_contextmenu.js @@ -0,0 +1,996 @@ +"use strict"; + +let contextMenu; +let LOGIN_FILL_ITEMS = [ + "---", null, + "fill-login", null, + [ + "fill-login-no-logins", false, + "---", null, + "fill-login-saved-passwords", true + ], null, +]; +let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled"); +let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled"); + +const example_base = "http://example.com/browser/browser/base/content/test/general/"; +const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/"; + +Services.scriptloader.loadSubScript(chrome_base + "contextmenu_common.js", this); + +// Below are test cases for XUL element +add_task(function* test_xul_text_link_label() { + let url = chrome_base + "subtst_contextmenu_xul.xul"; + + yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + yield test_contextmenu("#test-xul-text-link-label", + ["context-openlinkintab", true, + ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []), + // We need a blank entry here because the containers submenu is + // dynamically generated with no ids. + ...(hasContainers ? ["", null] : []), + "context-openlink", true, + "context-openlinkprivate", true, + "---", null, + "context-bookmarklink", true, + "context-savelink", true, + ...(hasPocket ? ["context-savelinktopocket", true] : []), + "context-copylink", true, + "context-searchselect", true + ] + ); + + // Clean up so won't affect HTML element test cases + lastElementSelector = null; + gBrowser.removeCurrentTab(); +}); + +// Below are test cases for HTML element + +add_task(function* test_setup_html() { + let url = example_base + "subtst_contextmenu.html"; + + yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let videoIframe = doc.querySelector("#test-video-in-iframe"); + let video = videoIframe.contentDocument.querySelector("video"); + let awaitPause = ContentTaskUtils.waitForEvent(video, "pause"); + video.pause(); + yield awaitPause; + + let audioIframe = doc.querySelector("#test-audio-in-iframe"); + // media documents always use a <video> tag. + let audio = audioIframe.contentDocument.querySelector("video"); + awaitPause = ContentTaskUtils.waitForEvent(audio, "pause"); + audio.pause(); + yield awaitPause; + }); +}); + +let plainTextItems; +add_task(function* test_plaintext() { + plainTextItems = ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ]; + yield test_contextmenu("#test-text", plainTextItems); +}); + +add_task(function* test_link() { + yield test_contextmenu("#test-link", + ["context-openlinkintab", true, + ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []), + // We need a blank entry here because the containers submenu is + // dynamically generated with no ids. + ...(hasContainers ? ["", null] : []), + "context-openlink", true, + "context-openlinkprivate", true, + "---", null, + "context-bookmarklink", true, + "context-savelink", true, + ...(hasPocket ? ["context-savelinktopocket", true] : []), + "context-copylink", true, + "context-searchselect", true + ] + ); +}); + +add_task(function* test_mailto() { + yield test_contextmenu("#test-mailto", + ["context-copyemail", true, + "context-searchselect", true + ] + ); +}); + +add_task(function* test_image() { + yield test_contextmenu("#test-image", + ["context-viewimage", true, + "context-copyimage-contents", true, + "context-copyimage", true, + "---", null, + "context-saveimage", true, + "context-sendimage", true, + "context-setDesktopBackground", true, + "context-viewimageinfo", true + ] + ); +}); + +add_task(function* test_canvas() { + yield test_contextmenu("#test-canvas", + ["context-viewimage", true, + "context-saveimage", true, + "context-selectall", true + ] + ); +}); + +add_task(function* test_video_ok() { + yield test_contextmenu("#test-video-ok", + ["context-media-play", true, + "context-media-mute", true, + "context-media-playbackrate", null, + ["context-media-playbackrate-050x", true, + "context-media-playbackrate-100x", true, + "context-media-playbackrate-125x", true, + "context-media-playbackrate-150x", true, + "context-media-playbackrate-200x", true], null, + "context-media-loop", true, + "context-media-hidecontrols", true, + "context-video-fullscreen", true, + "---", null, + "context-viewvideo", true, + "context-copyvideourl", true, + "---", null, + "context-savevideo", true, + "context-video-saveimage", true, + "context-sendvideo", true, + "context-castvideo", null, + [], null + ] + ); +}); + +add_task(function* test_audio_in_video() { + yield test_contextmenu("#test-audio-in-video", + ["context-media-play", true, + "context-media-mute", true, + "context-media-playbackrate", null, + ["context-media-playbackrate-050x", true, + "context-media-playbackrate-100x", true, + "context-media-playbackrate-125x", true, + "context-media-playbackrate-150x", true, + "context-media-playbackrate-200x", true], null, + "context-media-loop", true, + "context-media-showcontrols", true, + "---", null, + "context-copyaudiourl", true, + "---", null, + "context-saveaudio", true, + "context-sendaudio", true + ] + ); +}); + +add_task(function* test_video_bad() { + yield test_contextmenu("#test-video-bad", + ["context-media-play", false, + "context-media-mute", false, + "context-media-playbackrate", null, + ["context-media-playbackrate-050x", false, + "context-media-playbackrate-100x", false, + "context-media-playbackrate-125x", false, + "context-media-playbackrate-150x", false, + "context-media-playbackrate-200x", false], null, + "context-media-loop", true, + "context-media-hidecontrols", false, + "context-video-fullscreen", false, + "---", null, + "context-viewvideo", true, + "context-copyvideourl", true, + "---", null, + "context-savevideo", true, + "context-video-saveimage", false, + "context-sendvideo", true, + "context-castvideo", null, + [], null + ] + ); +}); + +add_task(function* test_video_bad2() { + yield test_contextmenu("#test-video-bad2", + ["context-media-play", false, + "context-media-mute", false, + "context-media-playbackrate", null, + ["context-media-playbackrate-050x", false, + "context-media-playbackrate-100x", false, + "context-media-playbackrate-125x", false, + "context-media-playbackrate-150x", false, + "context-media-playbackrate-200x", false], null, + "context-media-loop", true, + "context-media-hidecontrols", false, + "context-video-fullscreen", false, + "---", null, + "context-viewvideo", false, + "context-copyvideourl", false, + "---", null, + "context-savevideo", false, + "context-video-saveimage", false, + "context-sendvideo", false, + "context-castvideo", null, + [], null + ] + ); +}); + +add_task(function* test_iframe() { + yield test_contextmenu("#test-iframe", + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "frame", null, + ["context-showonlythisframe", true, + "context-openframeintab", true, + "context-openframe", true, + "---", null, + "context-reloadframe", true, + "---", null, + "context-bookmarkframe", true, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframesource", true, + "context-viewframeinfo", true], null, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ] + ); +}); + +add_task(function* test_video_in_iframe() { + yield test_contextmenu("#test-video-in-iframe", + ["context-media-play", true, + "context-media-mute", true, + "context-media-playbackrate", null, + ["context-media-playbackrate-050x", true, + "context-media-playbackrate-100x", true, + "context-media-playbackrate-125x", true, + "context-media-playbackrate-150x", true, + "context-media-playbackrate-200x", true], null, + "context-media-loop", true, + "context-media-hidecontrols", true, + "context-video-fullscreen", true, + "---", null, + "context-viewvideo", true, + "context-copyvideourl", true, + "---", null, + "context-savevideo", true, + "context-video-saveimage", true, + "context-sendvideo", true, + "context-castvideo", null, + [], null, + "frame", null, + ["context-showonlythisframe", true, + "context-openframeintab", true, + "context-openframe", true, + "---", null, + "context-reloadframe", true, + "---", null, + "context-bookmarkframe", true, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframeinfo", true], null] + ); +}); + +add_task(function* test_audio_in_iframe() { + yield test_contextmenu("#test-audio-in-iframe", + ["context-media-play", true, + "context-media-mute", true, + "context-media-playbackrate", null, + ["context-media-playbackrate-050x", true, + "context-media-playbackrate-100x", true, + "context-media-playbackrate-125x", true, + "context-media-playbackrate-150x", true, + "context-media-playbackrate-200x", true], null, + "context-media-loop", true, + "---", null, + "context-copyaudiourl", true, + "---", null, + "context-saveaudio", true, + "context-sendaudio", true, + "frame", null, + ["context-showonlythisframe", true, + "context-openframeintab", true, + "context-openframe", true, + "---", null, + "context-reloadframe", true, + "---", null, + "context-bookmarkframe", true, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframeinfo", true], null] + ); +}); + +add_task(function* test_image_in_iframe() { + yield test_contextmenu("#test-image-in-iframe", + ["context-viewimage", true, + "context-copyimage-contents", true, + "context-copyimage", true, + "---", null, + "context-saveimage", true, + "context-sendimage", true, + "context-setDesktopBackground", true, + "context-viewimageinfo", true, + "frame", null, + ["context-showonlythisframe", true, + "context-openframeintab", true, + "context-openframe", true, + "---", null, + "context-reloadframe", true, + "---", null, + "context-bookmarkframe", true, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframeinfo", true], null] + ); +}); + +add_task(function* test_textarea() { + // Disabled since this is seeing spell-check-enabled + // instead of spell-add-dictionaries-main + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-textarea", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-add-dictionaries-main", true, + ], + { + skipFocusChange: true, + } + ); + */ +}); + +add_task(function* test_textarea_spellcheck() { + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-textarea", + ["*chubbiness", true, // spelling suggestion + "spell-add-to-dictionary", true, + "---", null, + "context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true, + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null + ], + { + waitForSpellCheck: true, + offsetX: 6, + offsetY: 6, + postCheckContextMenuFn() { + document.getElementById("spell-add-to-dictionary").doCommand(); + } + } + ); + */ +}); + +add_task(function* test_plaintext2() { + yield test_contextmenu("#test-text", plainTextItems); +}); + +add_task(function* test_undo_add_to_dictionary() { + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-textarea", + ["spell-undo-add-to-dictionary", true, + "---", null, + "context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true, + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null + ], + { + waitForSpellCheck: true, + postCheckContextMenuFn() { + document.getElementById("spell-undo-add-to-dictionary") + .doCommand(); + } + } + ); + */ +}); + +add_task(function* test_contenteditable() { + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-contenteditable", + ["spell-no-suggestions", false, + "spell-add-to-dictionary", true, + "---", null, + "context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true, + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null + ], + {waitForSpellCheck: true} + ); + */ +}); + +add_task(function* test_copylinkcommand() { + yield test_contextmenu("#test-link", null, { + postCheckContextMenuFn: function*() { + document.commandDispatcher + .getControllerForCommand("cmd_copyLink") + .doCommand("cmd_copyLink"); + + // The easiest way to check the clipboard is to paste the contents + // into a textbox. + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let input = doc.getElementById("test-input"); + input.focus(); + input.value = ""; + }); + document.commandDispatcher + .getControllerForCommand("cmd_paste") + .doCommand("cmd_paste"); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let input = doc.getElementById("test-input"); + Assert.equal(input.value, "http://mozilla.com/", "paste for command cmd_paste"); + }); + } + }); +}); + +add_task(function* test_pagemenu() { + yield test_contextmenu("#test-pagemenu", + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "+Plain item", {type: "", icon: "", checked: false, disabled: false}, + "+Disabled item", {type: "", icon: "", checked: false, disabled: true}, + "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false}, + "---", null, + "+Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false}, + "---", null, + "+Radio1", {type: "checkbox", icon: "", checked: true, disabled: false}, + "+Radio2", {type: "checkbox", icon: "", checked: false, disabled: false}, + "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false}, + "---", null, + "+Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false}, + "+Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false}, + "---", null, + "generated-submenu-1", true, + ["+Radio1", {type: "checkbox", icon: "", checked: false, disabled: false}, + "+Radio2", {type: "checkbox", icon: "", checked: true, disabled: false}, + "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false}, + "---", null, + "+Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ], + {postCheckContextMenuFn: function*() { + let item = contextMenu.getElementsByAttribute("generateditemid", "1")[0]; + ok(item, "Got generated XUL menu item"); + item.doCommand(); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let pagemenu = content.document.getElementById("test-pagemenu"); + Assert.ok(!pagemenu.hasAttribute("hopeless"), "attribute got removed"); + }); + } + }); +}); + +add_task(function* test_dom_full_screen() { + yield test_contextmenu("#test-dom-full-screen", + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-leave-dom-fullscreen", true, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ], + { + shiftkey: true, + *preCheckContextMenuFn() { + yield pushPrefs(["full-screen-api.allow-trusted-requests-only", false], + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"]) + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let win = doc.defaultView; + let full_screen_element = doc.getElementById("test-dom-full-screen"); + let awaitFullScreenChange = + ContentTaskUtils.waitForEvent(win, "fullscreenchange"); + full_screen_element.requestFullscreen(); + yield awaitFullScreenChange; + }); + }, + *postCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let win = content.document.defaultView; + let awaitFullScreenChange = + ContentTaskUtils.waitForEvent(win, "fullscreenchange"); + content.document.exitFullscreen(); + yield awaitFullScreenChange; + }); + } + } + ); +}); + +add_task(function* test_pagemenu2() { + yield test_contextmenu("#test-text", + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ], + {shiftkey: true} + ); +}); + +add_task(function* test_select_text() { + yield test_contextmenu("#test-select-text", + ["context-copy", true, + "context-selectall", true, + "---", null, + "context-searchselect", true, + "context-viewpartialsource-selection", true + ], + { + offsetX: 6, + offsetY: 6, + *preCheckContextMenuFn() { + yield selectText("#test-select-text"); + } + } + ); +}); + +add_task(function* test_select_text_link() { + yield test_contextmenu("#test-select-text-link", + ["context-openlinkincurrent", true, + "context-openlinkintab", true, + ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []), + // We need a blank entry here because the containers submenu is + // dynamically generated with no ids. + ...(hasContainers ? ["", null] : []), + "context-openlink", true, + "context-openlinkprivate", true, + "---", null, + "context-bookmarklink", true, + "context-savelink", true, + "context-copy", true, + "context-selectall", true, + "---", null, + "context-searchselect", true, + "context-viewpartialsource-selection", true + ], + { + offsetX: 6, + offsetY: 6, + *preCheckContextMenuFn() { + yield selectText("#test-select-text-link"); + }, + *postCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let win = content.document.defaultView; + win.getSelection().removeAllRanges(); + }); + } + } + ); +}); + +add_task(function* test_imagelink() { + yield test_contextmenu("#test-image-link", + ["context-openlinkintab", true, + ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []), + // We need a blank entry here because the containers submenu is + // dynamically generated with no ids. + ...(hasContainers ? ["", null] : []), + "context-openlink", true, + "context-openlinkprivate", true, + "---", null, + "context-bookmarklink", true, + "context-savelink", true, + ...(hasPocket ? ["context-savelinktopocket", true] : []), + "context-copylink", true, + "---", null, + "context-viewimage", true, + "context-copyimage-contents", true, + "context-copyimage", true, + "---", null, + "context-saveimage", true, + "context-sendimage", true, + "context-setDesktopBackground", true, + "context-viewimageinfo", true + ] + ); +}); + +add_task(function* test_select_input_text() { + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-select-input-text", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", true, + "---", null, + "context-selectall", true, + "context-searchselect", true, + "---", null, + "spell-check-enabled", true + ].concat(LOGIN_FILL_ITEMS), + { + *preCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let win = doc.defaultView; + win.getSelection().removeAllRanges(); + let element = doc.querySelector("#test-select-input-text"); + element.select(); + }); + } + } + ); + */ +}); + +add_task(function* test_select_input_text_password() { + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-select-input-text-type-password", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", true, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true, + // spell checker is shown on input[type="password"] on this testcase + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null + ].concat(LOGIN_FILL_ITEMS), + { + *preCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let win = doc.defaultView; + win.getSelection().removeAllRanges(); + let element = doc.querySelector("#test-select-input-text-type-password"); + element.select(); + }); + }, + *postCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let win = content.document.defaultView; + win.getSelection().removeAllRanges(); + }); + } + } + ); + */ +}); + +add_task(function* test_click_to_play_blocked_plugin() { + yield test_contextmenu("#test-plugin", + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-ctp-play", true, + "context-ctp-hide", true, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ], + { + preCheckContextMenuFn: function*() { + pushPrefs(["plugins.click_to_play", true]); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + }, + postCheckContextMenuFn: function*() { + getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED; + } + } + ); +}); + +add_task(function* test_longdesc() { + yield test_contextmenu("#test-longdesc", + ["context-viewimage", true, + "context-copyimage-contents", true, + "context-copyimage", true, + "---", null, + "context-saveimage", true, + "context-sendimage", true, + "context-setDesktopBackground", true, + "context-viewimageinfo", true, + "context-viewimagedesc", true + ] + ); +}); + +add_task(function* test_srcdoc() { + yield test_contextmenu("#test-srcdoc", + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "frame", null, + ["context-reloadframe", true, + "---", null, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframesource", true, + "context-viewframeinfo", true], null, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ] + ); +}); + +add_task(function* test_input_spell_false() { + todo(false, "spell checker tests are failing, bug 1246296"); + return; + + /* + yield test_contextmenu("#test-contenteditable-spellcheck-false", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + ] + ); + */ +}); + +const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ]; + +add_task(function* test_plaintext_sendpagetodevice() { + if (!gFxAccounts.sendTabToDeviceEnabled) { + return; + } + const oldGetter = setupRemoteClientsFixture(remoteClientsFixture); + + let plainTextItemsWithSendPage = + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-sendpagetodevice", true, + ["*Foo", true, + "*Bar", true, + "---", null, + "*All Devices", true], null, + "---", null, + "context-viewbgimage", false, + "context-selectall", true, + "---", null, + "context-viewsource", true, + "context-viewinfo", true + ]; + yield test_contextmenu("#test-text", plainTextItemsWithSendPage, { + *onContextMenuShown() { + yield openMenuItemSubmenu("context-sendpagetodevice"); + } + }); + + restoreRemoteClients(oldGetter); +}); + +add_task(function* test_link_sendlinktodevice() { + if (!gFxAccounts.sendTabToDeviceEnabled) { + return; + } + const oldGetter = setupRemoteClientsFixture(remoteClientsFixture); + + yield test_contextmenu("#test-link", + ["context-openlinkintab", true, + ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []), + // We need a blank entry here because the containers submenu is + // dynamically generated with no ids. + ...(hasContainers ? ["", null] : []), + "context-openlink", true, + "context-openlinkprivate", true, + "---", null, + "context-bookmarklink", true, + "context-savelink", true, + ...(hasPocket ? ["context-savelinktopocket", true] : []), + "context-copylink", true, + "context-searchselect", true, + "---", null, + "context-sendlinktodevice", true, + ["*Foo", true, + "*Bar", true, + "---", null, + "*All Devices", true], null, + ], + { + *onContextMenuShown() { + yield openMenuItemSubmenu("context-sendlinktodevice"); + } + }); + + restoreRemoteClients(oldGetter); +}); + +add_task(function* test_cleanup_html() { + gBrowser.removeCurrentTab(); +}); + +/** + * Selects the text of the element that matches the provided `selector` + * + * @param {String} selector + * A selector passed to querySelector to find + * the element that will be referenced. + */ +function* selectText(selector) { + yield ContentTask.spawn(gBrowser.selectedBrowser, selector, function*(contentSelector) { + info(`Selecting text of ${contentSelector}`); + let doc = content.document; + let win = doc.defaultView; + win.getSelection().removeAllRanges(); + let div = doc.createRange(); + let element = doc.querySelector(contentSelector); + Assert.ok(element, "Found element to select text from"); + div.setStartBefore(element); + div.setEndAfter(element); + win.getSelection().addRange(div); + }); +} diff --git a/browser/base/content/test/general/browser_contextmenu_childprocess.js b/browser/base/content/test/general/browser_contextmenu_childprocess.js new file mode 100644 index 000000000..3d52be9ab --- /dev/null +++ b/browser/base/content/test/general/browser_contextmenu_childprocess.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const gBaseURL = "https://example.com/browser/browser/base/content/test/general/"; + +add_task(function *() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gBaseURL + "subtst_contextmenu.html"); + + let contextMenu = document.getElementById("contentAreaContextMenu"); + + // Get the point of the element with the page menu (test-pagemenu) and + // synthesize a right mouse click there. + let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouse("#test-pagemenu", 5, 5, { type : "contextmenu", button : 2 }, tab.linkedBrowser); + yield popupShownPromise; + + checkMenu(contextMenu); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + contextMenu.hidePopup(); + yield popupHiddenPromise; + + yield BrowserTestUtils.removeTab(tab); +}); + +function checkItems(menuitem, arr) +{ + for (let i = 0; i < arr.length; i += 2) { + let str = arr[i]; + let details = arr[i + 1]; + if (str == "---") { + is(menuitem.localName, "menuseparator", "menuseparator"); + } + else if ("children" in details) { + is(menuitem.localName, "menu", "submenu"); + is(menuitem.getAttribute("label"), str, str + " label"); + checkItems(menuitem.firstChild.firstChild, details.children); + } + else { + is(menuitem.localName, "menuitem", str + " menuitem"); + + is(menuitem.getAttribute("label"), str, str + " label"); + is(menuitem.getAttribute("type"), details.type, str + " type"); + is(menuitem.getAttribute("image"), details.icon ? gBaseURL + details.icon : "", str + " icon"); + + if (details.checked) + is(menuitem.getAttribute("checked"), "true", str + " checked"); + else + ok(!menuitem.hasAttribute("checked"), str + " checked"); + + if (details.disabled) + is(menuitem.getAttribute("disabled"), "true", str + " disabled"); + else + ok(!menuitem.hasAttribute("disabled"), str + " disabled"); + } + + menuitem = menuitem.nextSibling; + } +} + +function checkMenu(contextMenu) +{ + let items = [ "Plain item", {type: "", icon: "", checked: false, disabled: false}, + "Disabled item", {type: "", icon: "", checked: false, disabled: true}, + "Item w/ textContent", {type: "", icon: "", checked: false, disabled: false}, + "---", null, + "Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false}, + "---", null, + "Radio1", {type: "checkbox", icon: "", checked: true, disabled: false}, + "Radio2", {type: "checkbox", icon: "", checked: false, disabled: false}, + "Radio3", {type: "checkbox", icon: "", checked: false, disabled: false}, + "---", null, + "Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false}, + "Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false}, + "---", null, + "Submenu", { children: + ["Radio1", {type: "checkbox", icon: "", checked: false, disabled: false}, + "Radio2", {type: "checkbox", icon: "", checked: true, disabled: false}, + "Radio3", {type: "checkbox", icon: "", checked: false, disabled: false}, + "---", null, + "Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}] } + ]; + checkItems(contextMenu.childNodes[2], items); +} diff --git a/browser/base/content/test/general/browser_contextmenu_input.js b/browser/base/content/test/general/browser_contextmenu_input.js new file mode 100644 index 000000000..cfc7b7529 --- /dev/null +++ b/browser/base/content/test/general/browser_contextmenu_input.js @@ -0,0 +1,243 @@ +"use strict"; + +let contextMenu; +let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled"); + +add_task(function* test_setup() { + const example_base = "http://example.com/browser/browser/base/content/test/general/"; + const url = example_base + "subtst_contextmenu_input.html"; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/"; + const contextmenu_common = chrome_base + "contextmenu_common.js"; + Services.scriptloader.loadSubScript(contextmenu_common, this); +}); + +add_task(function* test_text_input() { + yield test_contextmenu("#input_text", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", false, + "---", null, + "spell-check-enabled", true]); +}); + +add_task(function* test_text_input_spellcheck() { + yield test_contextmenu("#input_spellcheck_no_value", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", false, + "---", null, + "spell-check-enabled", true, + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null], + { + waitForSpellCheck: true, + // Need to dynamically add/remove the "password" type or LoginManager + // will think that the form inputs on the page are part of a login + // and will add fill-login context menu items. + *preCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let input = doc.getElementById("input_spellcheck_no_value"); + input.setAttribute("spellcheck", "true"); + input.clientTop; // force layout flush + }); + }, + } + ); +}); + +add_task(function* test_text_input_spellcheckwrong() { + yield test_contextmenu("#input_spellcheck_incorrect", + ["*prodigality", true, // spelling suggestion + "spell-add-to-dictionary", true, + "---", null, + "context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true, + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null], + {waitForSpellCheck: true} + ); +}); + +add_task(function* test_text_input_spellcheckcorrect() { + yield test_contextmenu("#input_spellcheck_correct", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true, + "spell-dictionaries", true, + ["spell-check-dictionary-en-US", true, + "---", null, + "spell-add-dictionaries", true], null], + {waitForSpellCheck: true} + ); +}); + +add_task(function* test_text_input_disabled() { + yield test_contextmenu("#input_disabled", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", true, + "---", null, + "spell-check-enabled", true], + {skipFocusChange: true} + ); +}); + +add_task(function* test_password_input() { + todo(false, "context-selectall is enabled on osx-e10s, and windows when" + + " it should be disabled"); + yield test_contextmenu("#input_password", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", null, + "---", null, + "fill-login", null, + ["fill-login-no-logins", false, + "---", null, + "fill-login-saved-passwords", true], null], + { + skipFocusChange: true, + // Need to dynamically add/remove the "password" type or LoginManager + // will think that the form inputs on the page are part of a login + // and will add fill-login context menu items. + *preCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let input = doc.getElementById("input_password"); + input.type = "password"; + input.clientTop; // force layout flush + }); + }, + *postCheckContextMenuFn() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let input = doc.getElementById("input_password"); + input.type = "text"; + input.clientTop; // force layout flush + }); + }, + } + ); +}); + +add_task(function* test_tel_email_url_number_input() { + todo(false, "context-selectall is enabled on osx-e10s, and windows when" + + " it should be disabled"); + for (let selector of ["#input_email", "#input_url", "#input_tel", "#input_number"]) { + yield test_contextmenu(selector, + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", null], + {skipFocusChange: true} + ); + } +}); + +add_task(function* test_date_time_color_range_month_week_datetimelocal_input() { + for (let selector of ["#input_date", "#input_time", "#input_color", + "#input_range", "#input_month", "#input_week", + "#input_datetime-local"]) { + yield test_contextmenu(selector, + ["context-navigation", null, + ["context-back", false, + "context-forward", false, + "context-reload", true, + "context-bookmarkpage", true], null, + "---", null, + "context-savepage", true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", null, + "context-viewbgimage", false, + "context-selectall", null, + "---", null, + "context-viewsource", true, + "context-viewinfo", true], + {skipFocusChange: true} + ); + } +}); + +add_task(function* test_search_input() { + todo(false, "context-selectall is enabled on osx-e10s, and windows when" + + " it should be disabled"); + yield test_contextmenu("#input_search", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", null, + "---", null, + "spell-check-enabled", true], + {skipFocusChange: true} + ); +}); + +add_task(function* test_text_input_readonly() { + todo(false, "context-selectall is enabled on osx-e10s, and windows when" + + " it should be disabled"); + todo(false, "spell-check should not be enabled for input[readonly]. see bug 1246296"); + yield test_contextmenu("#input_readonly", + ["context-undo", false, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "context-selectall", null], + {skipFocusChange: true} + ); +}); + +add_task(function* test_cleanup() { + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js b/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js new file mode 100644 index 000000000..00a06f53e --- /dev/null +++ b/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js @@ -0,0 +1,55 @@ +/* + * Description of the Test: + * We load an https page which uses a CSP including block-all-mixed-content. + * The page tries to load a script over http. We make sure the UI is not + * influenced when blocking the mixed content. In particular the page + * should still appear fully encrypted with a green lock. + */ + +const PRE_PATH = "https://example.com/browser/browser/base/content/test/general/"; +var gTestBrowser = null; + +// ------------------------------------------------------ +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); + finish(); +} + +// ------------------------------------------------------ +function verifyUInotDegraded() { + // make sure that not mixed content is loaded and also not blocked + assertMixedContentBlockingState( + gTestBrowser, + { activeLoaded: false, + activeBlocked: false, + passiveLoaded: false + } + ); + // clean up and finish test + cleanUpAfterTests(); +} + +// ------------------------------------------------------ +function runTests() { + var newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop(); + + // Starting the test + BrowserTestUtils.browserLoaded(gTestBrowser).then(verifyUInotDegraded); + var url = PRE_PATH + "file_csp_block_all_mixedcontent.html"; + gTestBrowser.loadURI(url); +} + +// ------------------------------------------------------ +function test() { + // Performing async calls, e.g. 'onload', we have to wait till all of them finished + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + { 'set': [["security.mixed_content.block_active_content", true]] }, + function() { runTests(); } + ); +} diff --git a/browser/base/content/test/general/browser_ctrlTab.js b/browser/base/content/test/general/browser_ctrlTab.js new file mode 100644 index 000000000..d16aaeca4 --- /dev/null +++ b/browser/base/content/test/general/browser_ctrlTab.js @@ -0,0 +1,185 @@ +add_task(function* () { + gPrefService.setBoolPref("browser.ctrlTab.previews", true); + + gBrowser.addTab(); + gBrowser.addTab(); + gBrowser.addTab(); + + checkTabs(4); + + yield ctrlTabTest([2], 1, 0); + yield ctrlTabTest([2, 3, 1], 2, 2); + yield ctrlTabTest([], 4, 2); + + { + let selectedIndex = gBrowser.tabContainer.selectedIndex; + yield pressCtrlTab(); + yield pressCtrlTab(true); + yield releaseCtrl(); + is(gBrowser.tabContainer.selectedIndex, selectedIndex, + "Ctrl+Tab -> Ctrl+Shift+Tab keeps the selected tab"); + } + + { // test for bug 445369 + let tabs = gBrowser.tabs.length; + yield pressCtrlTab(); + yield synthesizeCtrlW(); + is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes one tab"); + yield releaseCtrl(); + } + + { // test for bug 667314 + let tabs = gBrowser.tabs.length; + yield pressCtrlTab(); + yield pressCtrlTab(true); + yield synthesizeCtrlW(); + is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes the selected tab"); + yield releaseCtrl(); + } + + gBrowser.addTab(); + checkTabs(3); + yield ctrlTabTest([2, 1, 0], 7, 1); + + { // test for bug 1292049 + let tabToClose = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:buildconfig"); + checkTabs(4); + selectTabs([0, 1, 2, 3]); + + yield BrowserTestUtils.removeTab(tabToClose); + checkTabs(3); + undoCloseTab(); + checkTabs(4); + is(gBrowser.tabContainer.selectedIndex, 3, "tab is selected after closing and restoring it"); + + yield ctrlTabTest([], 1, 2); + } + + { // test for bug 445369 + checkTabs(4); + selectTabs([1, 2, 0]); + + let selectedTab = gBrowser.selectedTab; + let tabToRemove = gBrowser.tabs[1]; + + yield pressCtrlTab(); + yield pressCtrlTab(); + yield synthesizeCtrlW(); + ok(!tabToRemove.parentNode, + "Ctrl+Tab*2 -> Ctrl+W removes the second most recently selected tab"); + + yield pressCtrlTab(true); + yield pressCtrlTab(true); + yield releaseCtrl(); + ok(selectedTab.selected, + "Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab"); + } + gBrowser.removeTab(gBrowser.tabContainer.lastChild); + checkTabs(2); + + yield ctrlTabTest([1], 1, 0); + + gBrowser.removeTab(gBrowser.tabContainer.lastChild); + checkTabs(1); + + { // test for bug 445768 + let focusedWindow = document.commandDispatcher.focusedWindow; + let eventConsumed = true; + let detectKeyEvent = function (event) { + eventConsumed = event.defaultPrevented; + }; + document.addEventListener("keypress", detectKeyEvent, false); + yield pressCtrlTab(); + document.removeEventListener("keypress", detectKeyEvent, false); + ok(eventConsumed, "Ctrl+Tab consumed by the tabbed browser if one tab is open"); + is(focusedWindow, document.commandDispatcher.focusedWindow, + "Ctrl+Tab doesn't change focus if one tab is open"); + } + + // cleanup + if (gPrefService.prefHasUserValue("browser.ctrlTab.previews")) + gPrefService.clearUserPref("browser.ctrlTab.previews"); + + /* private utility functions */ + + function* pressCtrlTab(aShiftKey) { + let promise; + if (!isOpen() && canOpen()) { + promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown"); + } else { + promise = BrowserTestUtils.waitForEvent(document, "keyup"); + } + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey }); + return promise; + } + + function* releaseCtrl() { + let promise; + if (isOpen()) { + promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden"); + } else { + promise = BrowserTestUtils.waitForEvent(document, "keyup"); + } + EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }); + return promise; + } + + function* synthesizeCtrlW() { + let promise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose"); + EventUtils.synthesizeKey("w", { ctrlKey: true }); + return promise; + } + + function isOpen() { + return ctrlTab.isOpen; + } + + function canOpen() { + return gPrefService.getBoolPref("browser.ctrlTab.previews") && gBrowser.tabs.length > 2; + } + + function checkTabs(aTabs) { + is(gBrowser.tabs.length, aTabs, "number of open tabs should be " + aTabs); + } + + function selectTabs(tabs) { + tabs.forEach(function (index) { + gBrowser.selectedTab = gBrowser.tabs[index]; + }); + } + + function* ctrlTabTest(tabsToSelect, tabTimes, expectedIndex) { + selectTabs(tabsToSelect); + + var indexStart = gBrowser.tabContainer.selectedIndex; + var tabCount = gBrowser.tabs.length; + var normalized = tabTimes % tabCount; + var where = normalized == 1 ? "back to the previously selected tab" : + normalized + " tabs back in most-recently-selected order"; + + for (let i = 0; i < tabTimes; i++) { + yield pressCtrlTab(); + + if (tabCount > 2) + is(gBrowser.tabContainer.selectedIndex, indexStart, + "Selected tab doesn't change while tabbing"); + } + + if (tabCount > 2) { + ok(isOpen(), + "With " + tabCount + " tabs open, Ctrl+Tab opens the preview panel"); + + yield releaseCtrl(); + + ok(!isOpen(), + "Releasing Ctrl closes the preview panel"); + } else { + ok(!isOpen(), + "With " + tabCount + " tabs open, Ctrl+Tab doesn't open the preview panel"); + } + + is(gBrowser.tabContainer.selectedIndex, expectedIndex, + "With "+ tabCount +" tabs open and tab " + indexStart + + " selected, Ctrl+Tab*" + tabTimes + " goes " + where); + } +}); diff --git a/browser/base/content/test/general/browser_datachoices_notification.js b/browser/base/content/test/general/browser_datachoices_notification.js new file mode 100644 index 000000000..360728b4c --- /dev/null +++ b/browser/base/content/test/general/browser_datachoices_notification.js @@ -0,0 +1,221 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Pass an empty scope object to the import to prevent "leaked window property" +// errors in tests. +var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences; +var TelemetryReportingPolicy = + Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicy; + +const PREF_BRANCH = "datareporting.policy."; +const PREF_BYPASS_NOTIFICATION = PREF_BRANCH + "dataSubmissionPolicyBypassNotification"; +const PREF_CURRENT_POLICY_VERSION = PREF_BRANCH + "currentPolicyVersion"; +const PREF_ACCEPTED_POLICY_VERSION = PREF_BRANCH + "dataSubmissionPolicyAcceptedVersion"; +const PREF_ACCEPTED_POLICY_DATE = PREF_BRANCH + "dataSubmissionPolicyNotifiedTime"; + +const TEST_POLICY_VERSION = 37; + +function fakeShowPolicyTimeout(set, clear) { + let reportingPolicy = + Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).Policy; + reportingPolicy.setShowInfobarTimeout = set; + reportingPolicy.clearShowInfobarTimeout = clear; +} + +function sendSessionRestoredNotification() { + let reportingPolicyImpl = + Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicyImpl; + reportingPolicyImpl.observe(null, "sessionstore-windows-restored", null); +} + +/** + * Wait for a tick. + */ +function promiseNextTick() { + return new Promise(resolve => executeSoon(resolve)); +} + +/** + * Wait for a notification to be shown in a notification box. + * @param {Object} aNotificationBox The notification box. + * @return {Promise} Resolved when the notification is displayed. + */ +function promiseWaitForAlertActive(aNotificationBox) { + let deferred = PromiseUtils.defer(); + aNotificationBox.addEventListener("AlertActive", function onActive() { + aNotificationBox.removeEventListener("AlertActive", onActive, true); + deferred.resolve(); + }); + return deferred.promise; +} + +/** + * Wait for a notification to be closed. + * @param {Object} aNotification The notification. + * @return {Promise} Resolved when the notification is closed. + */ +function promiseWaitForNotificationClose(aNotification) { + let deferred = PromiseUtils.defer(); + waitForNotificationClose(aNotification, deferred.resolve); + return deferred.promise; +} + +function triggerInfoBar(expectedTimeoutMs) { + let showInfobarCallback = null; + let timeoutMs = null; + fakeShowPolicyTimeout((callback, timeout) => { + showInfobarCallback = callback; + timeoutMs = timeout; + }, () => {}); + sendSessionRestoredNotification(); + Assert.ok(!!showInfobarCallback, "Must have a timer callback."); + if (expectedTimeoutMs !== undefined) { + Assert.equal(timeoutMs, expectedTimeoutMs, "Timeout should match"); + } + showInfobarCallback(); +} + +var checkInfobarButton = Task.async(function* (aNotification) { + // Check that the button on the data choices infobar does the right thing. + let buttons = aNotification.getElementsByTagName("button"); + Assert.equal(buttons.length, 1, "There is 1 button in the data reporting notification."); + let button = buttons[0]; + + // Add an observer to ensure the "advanced" pane opened (but don't bother + // closing it - we close the entire window when done.) + let paneLoadedPromise = promiseTopicObserved("advanced-pane-loaded"); + + // Click on the button. + button.click(); + + // Wait for the preferences panel to open. + yield paneLoadedPromise; + yield promiseNextTick(); +}); + +add_task(function* setup() { + const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, true); + const currentPolicyVersion = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1); + + // Register a cleanup function to reset our preferences. + registerCleanupFunction(() => { + Preferences.set(PREF_BYPASS_NOTIFICATION, bypassNotification); + Preferences.set(PREF_CURRENT_POLICY_VERSION, currentPolicyVersion); + + return closeAllNotifications(); + }); + + // Don't skip the infobar visualisation. + Preferences.set(PREF_BYPASS_NOTIFICATION, false); + // Set the current policy version. + Preferences.set(PREF_CURRENT_POLICY_VERSION, TEST_POLICY_VERSION); +}); + +function clearAcceptedPolicy() { + // Reset the accepted policy. + Preferences.reset(PREF_ACCEPTED_POLICY_VERSION); + Preferences.reset(PREF_ACCEPTED_POLICY_DATE); +} + +add_task(function* test_single_window() { + clearAcceptedPolicy(); + + // Close all the notifications, then try to trigger the data choices infobar. + yield closeAllNotifications(); + + let notificationBox = document.getElementById("global-notificationbox"); + + // Make sure that we have a coherent initial state. + Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), 0, + "No version should be set on init."); + Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0), 0, + "No date should be set on init."); + Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(), + "User not notified about datareporting policy."); + + let alertShownPromise = promiseWaitForAlertActive(notificationBox); + Assert.ok(!TelemetryReportingPolicy.canUpload(), + "User should not be allowed to upload."); + + // Wait for the infobar to be displayed. + triggerInfoBar(10 * 1000); + yield alertShownPromise; + + Assert.equal(notificationBox.allNotifications.length, 1, "Notification Displayed."); + Assert.ok(TelemetryReportingPolicy.canUpload(), "User should be allowed to upload now."); + + yield promiseNextTick(); + let promiseClosed = promiseWaitForNotificationClose(notificationBox.currentNotification); + yield checkInfobarButton(notificationBox.currentNotification); + yield promiseClosed; + + Assert.equal(notificationBox.allNotifications.length, 0, "No notifications remain."); + + // Check that we are still clear to upload and that the policy data is saved. + Assert.ok(TelemetryReportingPolicy.canUpload()); + Assert.equal(TelemetryReportingPolicy.testIsUserNotified(), true, + "User notified about datareporting policy."); + Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), TEST_POLICY_VERSION, + "Version pref set."); + Assert.greater(parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10), -1, + "Date pref set."); +}); + +add_task(function* test_multiple_windows() { + clearAcceptedPolicy(); + + // Close all the notifications, then try to trigger the data choices infobar. + yield closeAllNotifications(); + + // Ensure we see the notification on all windows and that action on one window + // results in dismiss on every window. + let otherWindow = yield BrowserTestUtils.openNewBrowserWindow(); + + // Get the notification box for both windows. + let notificationBoxes = [ + document.getElementById("global-notificationbox"), + otherWindow.document.getElementById("global-notificationbox") + ]; + + Assert.ok(notificationBoxes[1], "2nd window has a global notification box."); + + // Make sure that we have a coherent initial state. + Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), 0, "No version should be set on init."); + Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0), 0, "No date should be set on init."); + Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(), "User not notified about datareporting policy."); + + let showAlertPromises = [ + promiseWaitForAlertActive(notificationBoxes[0]), + promiseWaitForAlertActive(notificationBoxes[1]) + ]; + + Assert.ok(!TelemetryReportingPolicy.canUpload(), + "User should not be allowed to upload."); + + // Wait for the infobars. + triggerInfoBar(10 * 1000); + yield Promise.all(showAlertPromises); + + // Both notification were displayed. Close one and check that both gets closed. + let closeAlertPromises = [ + promiseWaitForNotificationClose(notificationBoxes[0].currentNotification), + promiseWaitForNotificationClose(notificationBoxes[1].currentNotification) + ]; + notificationBoxes[0].currentNotification.close(); + yield Promise.all(closeAlertPromises); + + // Close the second window we opened. + yield BrowserTestUtils.closeWindow(otherWindow); + + // Check that we are clear to upload and that the policy data us saved. + Assert.ok(TelemetryReportingPolicy.canUpload(), "User should be allowed to upload now."); + Assert.equal(TelemetryReportingPolicy.testIsUserNotified(), true, + "User notified about datareporting policy."); + Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), TEST_POLICY_VERSION, + "Version pref set."); + Assert.greater(parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10), -1, + "Date pref set."); +}); diff --git a/browser/base/content/test/general/browser_decoderDoctor.js b/browser/base/content/test/general/browser_decoderDoctor.js new file mode 100644 index 000000000..a37972160 --- /dev/null +++ b/browser/base/content/test/general/browser_decoderDoctor.js @@ -0,0 +1,122 @@ +"use strict"; + +function* test_decoder_doctor_notification(type, notificationMessage, options) { + yield BrowserTestUtils.withNewTab({ gBrowser }, function*(browser) { + let awaitNotificationBar = + BrowserTestUtils.waitForNotificationBar(gBrowser, browser, "decoder-doctor-notification"); + + yield ContentTask.spawn(browser, type, function*(aType) { + Services.obs.notifyObservers(content.window, + "decoder-doctor-notification", + JSON.stringify({type: aType, + isSolved: false, + decoderDoctorReportId: "test", + formats: "test"})); + }); + + let notification; + try { + notification = yield awaitNotificationBar; + } catch (ex) { + ok(false, ex); + return; + } + ok(notification, "Got decoder-doctor-notification notification"); + + is(notification.getAttribute("label"), notificationMessage, + "notification message should match expectation"); + let button = notification.childNodes[0]; + if (options && options.noLearnMoreButton) { + ok(!button, "There should not be a Learn More button"); + return; + } + + is(button.getAttribute("label"), gNavigatorBundle.getString("decoder.noCodecs.button"), + "notification button should be 'Learn more'"); + is(button.getAttribute("accesskey"), gNavigatorBundle.getString("decoder.noCodecs.accesskey"), + "notification button should have accesskey"); + + let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); + let url = baseURL + ((options && options.sumo) || + "fix-video-audio-problems-firefox-windows"); + let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, url); + button.click(); + let sumoTab = yield awaitNewTab; + yield BrowserTestUtils.removeTab(sumoTab); + }); +} + +add_task(function* test_adobe_cdm_not_found() { + // This is only sent on Windows. + if (AppConstants.platform != "win") { + return; + } + + let message; + if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) { + message = gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]); + } else { + message = gNavigatorBundle.getString("decoder.noCodecs.message"); + } + + yield test_decoder_doctor_notification("adobe-cdm-not-found", message); +}); + +add_task(function* test_adobe_cdm_not_activated() { + // This is only sent on Windows. + if (AppConstants.platform != "win") { + return; + } + + let message; + if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) { + message = gNavigatorBundle.getString("decoder.noCodecsXP.message"); + } else { + message = gNavigatorBundle.getString("decoder.noCodecs.message"); + } + + yield test_decoder_doctor_notification("adobe-cdm-not-activated", message); +}); + +add_task(function* test_platform_decoder_not_found() { + // Not sent on Windows XP. + if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) { + return; + } + + let message; + let isLinux = AppConstants.platform == "linux"; + if (isLinux) { + message = gNavigatorBundle.getString("decoder.noCodecsLinux.message"); + } else { + message = gNavigatorBundle.getString("decoder.noHWAcceleration.message"); + } + + yield test_decoder_doctor_notification("platform-decoder-not-found", + message, + {noLearnMoreButton: isLinux}); +}); + +add_task(function* test_cannot_initialize_pulseaudio() { + // This is only sent on Linux. + if (AppConstants.platform != "linux") { + return; + } + + let message = gNavigatorBundle.getString("decoder.noPulseAudio.message"); + yield test_decoder_doctor_notification("cannot-initialize-pulseaudio", + message, + {sumo: "fix-common-audio-and-video-issues"}); +}); + +add_task(function* test_unsupported_libavcodec() { + // This is only sent on Linux. + if (AppConstants.platform != "linux") { + return; + } + + let message = gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message"); + yield test_decoder_doctor_notification("unsupported-libavcodec", + message, + {noLearnMoreButton: true}); +}); diff --git a/browser/base/content/test/general/browser_devedition.js b/browser/base/content/test/general/browser_devedition.js new file mode 100644 index 000000000..06ee42e7e --- /dev/null +++ b/browser/base/content/test/general/browser_devedition.js @@ -0,0 +1,129 @@ +/* + * Testing changes for Developer Edition theme. + * A special stylesheet should be added to the browser.xul document + * when the firefox-devedition@mozilla.org lightweight theme + * is applied. + */ + +const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes"; +const PREF_DEVTOOLS_THEME = "devtools.theme"; +const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {}); + +LightweightThemeManager.clearBuiltInThemes(); +LightweightThemeManager.addBuiltInTheme(dummyLightweightTheme("firefox-devedition@mozilla.org")); + +registerCleanupFunction(() => { + // Set preferences back to their original values + LightweightThemeManager.currentTheme = null; + Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME); + Services.prefs.clearUserPref(PREF_LWTHEME_USED_THEMES); + + LightweightThemeManager.currentTheme = null; + LightweightThemeManager.clearBuiltInThemes(); +}); + +add_task(function* startTests() { + Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark"); + + info ("Setting the current theme to null"); + LightweightThemeManager.currentTheme = null; + ok (!DevEdition.isStyleSheetEnabled, "There is no devedition style sheet when no lw theme is applied."); + + info ("Adding a lightweight theme."); + LightweightThemeManager.currentTheme = dummyLightweightTheme("preview0"); + ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been removed when a lightweight theme is applied."); + + info ("Applying the devedition lightweight theme."); + let onAttributeAdded = waitForBrightTitlebarAttribute(); + LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org"); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been added when the devedition lightweight theme is applied"); + yield onAttributeAdded; + is (document.documentElement.getAttribute("brighttitlebarforeground"), "true", + "The brighttitlebarforeground attribute is set on the window."); + + info ("Unapplying all themes."); + LightweightThemeManager.currentTheme = null; + ok (!DevEdition.isStyleSheetEnabled, "There is no devedition style sheet when no lw theme is applied."); + + info ("Applying the devedition lightweight theme."); + onAttributeAdded = waitForBrightTitlebarAttribute(); + LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org"); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been added when the devedition lightweight theme is applied"); + yield onAttributeAdded; + ok (document.documentElement.hasAttribute("brighttitlebarforeground"), + "The brighttitlebarforeground attribute is set on the window with dark devtools theme."); +}); + +add_task(function* testDevtoolsTheme() { + info ("Checking stylesheet and :root attributes based on devtools theme."); + Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light"); + is (document.documentElement.getAttribute("devtoolstheme"), "light", + "The documentElement has an attribute based on devtools theme."); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the light devtools theme."); + ok (!document.documentElement.hasAttribute("brighttitlebarforeground"), + "The brighttitlebarforeground attribute is not set on the window with light devtools theme."); + + Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark"); + is (document.documentElement.getAttribute("devtoolstheme"), "dark", + "The documentElement has an attribute based on devtools theme."); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the dark devtools theme."); + is (document.documentElement.getAttribute("brighttitlebarforeground"), "true", + "The brighttitlebarforeground attribute is set on the window with dark devtools theme."); + + Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "foobar"); + is (document.documentElement.getAttribute("devtoolstheme"), "light", + "The documentElement has 'light' as a default for the devtoolstheme attribute"); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the foobar devtools theme."); + ok (!document.documentElement.hasAttribute("brighttitlebarforeground"), + "The brighttitlebarforeground attribute is not set on the window with light devtools theme."); +}); + +function dummyLightweightTheme(id) { + return { + id: id, + name: id, + headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png", + iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png", + textcolor: "red", + accentcolor: "blue" + }; +} + +add_task(function* testLightweightThemePreview() { + info ("Setting devedition to current and the previewing others"); + LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org"); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled."); + LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0")); + ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is not enabled after a lightweight theme preview."); + LightweightThemeManager.resetPreview(); + LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1")); + ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is not enabled after a second lightweight theme preview."); + LightweightThemeManager.resetPreview(); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled again after resetting the preview."); + LightweightThemeManager.currentTheme = null; + ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is gone after removing the current theme."); + + info ("Previewing the devedition theme"); + LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org")); + ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled."); + LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2")); + LightweightThemeManager.resetPreview(); + ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is now disabled after resetting the preview."); +}); + +// Use a mutation observer to wait for the brighttitlebarforeground +// attribute to change. Using this instead of waiting for the load +// event on the DevEdition styleSheet. +function waitForBrightTitlebarAttribute() { + return new Promise((resolve, reject) => { + let mutationObserver = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + if (mutation.attributeName == "brighttitlebarforeground") { + mutationObserver.disconnect(); + resolve(); + } + } + }); + mutationObserver.observe(document.documentElement, { attributes: true }); + }); +} diff --git a/browser/base/content/test/general/browser_discovery.js b/browser/base/content/test/general/browser_discovery.js new file mode 100644 index 000000000..23d44c6a9 --- /dev/null +++ b/browser/base/content/test/general/browser_discovery.js @@ -0,0 +1,162 @@ +var browser; + +function doc() { + return browser.contentDocument; +} + +function setHandlerFunc(aResultFunc) { + gBrowser.addEventListener("DOMLinkAdded", function (event) { + gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, false); + executeSoon(aResultFunc); + }, false); +} + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function (event) { + event.currentTarget.removeEventListener("load", arguments.callee, true); + iconDiscovery(); + }, true); + var rootDir = getRootDirectory(gTestPath); + content.location = rootDir + "discovery.html"; +} + +var iconDiscoveryTests = [ + { text: "rel icon discovered" }, + { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" }, + { rel: "ICON", text: "rel is case insensitive" }, + { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" }, + { href: "moz.png", text: "relative href works" }, + { href: "notthere.png", text: "404'd icon is removed properly" }, + { href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" }, + { type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" } +]; + +function runIconDiscoveryTest() { + var testCase = iconDiscoveryTests[0]; + var head = doc().getElementById("linkparent"); + var hasSrc = gBrowser.getIcon() != null; + if (testCase.pass) + ok(hasSrc, testCase.text); + else + ok(!hasSrc, testCase.text); + + head.removeChild(head.getElementsByTagName('link')[0]); + iconDiscoveryTests.shift(); + iconDiscovery(); // Run the next test. +} + +function iconDiscovery() { + if (iconDiscoveryTests.length) { + setHandlerFunc(runIconDiscoveryTest); + gBrowser.setIcon(gBrowser.selectedTab, null, + Services.scriptSecurityManager.getSystemPrincipal()); + + var testCase = iconDiscoveryTests[0]; + var head = doc().getElementById("linkparent"); + var link = doc().createElement("link"); + + var rootDir = getRootDirectory(gTestPath); + var rel = testCase.rel || "icon"; + var href = testCase.href || rootDir + "moz.png"; + var type = testCase.type || "image/png"; + if (testCase.pass == undefined) + testCase.pass = true; + + link.rel = rel; + link.href = href; + link.type = type; + head.appendChild(link); + } else { + searchDiscovery(); + } +} + +var searchDiscoveryTests = [ + { text: "rel search discovered" }, + { rel: "SEARCH", text: "rel is case insensitive" }, + { rel: "-search-", pass: false, text: "rel -search- not discovered" }, + { rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" }, + { href: "https://not.mozilla.com", text: "HTTPS ok" }, + { href: "ftp://not.mozilla.com", text: "FTP ok" }, + { href: "data:text/foo,foo", pass: false, text: "data URI not permitted" }, + { href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" }, + { type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" }, + { type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" }, + { type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" }, + { type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" }, + { rel: "search search search", count: 1, text: "only one engine should be added" } +]; + +function runSearchDiscoveryTest() { + var testCase = searchDiscoveryTests[0]; + var title = testCase.title || searchDiscoveryTests.length; + if (browser.engines) { + var hasEngine = (testCase.count) ? (browser.engines[0].title == title && + browser.engines.length == testCase.count) : + (browser.engines[0].title == title); + ok(hasEngine, testCase.text); + browser.engines = null; + } + else + ok(!testCase.pass, testCase.text); + + searchDiscoveryTests.shift(); + searchDiscovery(); // Run the next test. +} + +// This handler is called twice, once for each added link element. +// Only want to check once the second link element has been added. +var ranOnce = false; +function runMultipleEnginesTestAndFinalize() { + if (!ranOnce) { + ranOnce = true; + return; + } + ok(browser.engines, "has engines"); + is(browser.engines.length, 1, "only one engine"); + is(browser.engines[0].uri, "http://first.mozilla.com/search.xml", "first engine wins"); + + gBrowser.removeCurrentTab(); + finish(); +} + +function searchDiscovery() { + let head = doc().getElementById("linkparent"); + + if (searchDiscoveryTests.length) { + setHandlerFunc(runSearchDiscoveryTest); + let testCase = searchDiscoveryTests[0]; + let link = doc().createElement("link"); + + let rel = testCase.rel || "search"; + let href = testCase.href || "http://so.not.here.mozilla.com/search.xml"; + let type = testCase.type || "application/opensearchdescription+xml"; + let title = testCase.title || searchDiscoveryTests.length; + if (testCase.pass == undefined) + testCase.pass = true; + + link.rel = rel; + link.href = href; + link.type = type; + link.title = title; + head.appendChild(link); + } else { + setHandlerFunc(runMultipleEnginesTestAndFinalize); + setHandlerFunc(runMultipleEnginesTestAndFinalize); + // Test multiple engines with the same title + let link = doc().createElement("link"); + link.rel = "search"; + link.href = "http://first.mozilla.com/search.xml"; + link.type = "application/opensearchdescription+xml"; + link.title = "Test Engine"; + let link2 = link.cloneNode(false); + link2.href = "http://second.mozilla.com/search.xml"; + + head.appendChild(link); + head.appendChild(link2); + } +} diff --git a/browser/base/content/test/general/browser_documentnavigation.js b/browser/base/content/test/general/browser_documentnavigation.js new file mode 100644 index 000000000..eb789d076 --- /dev/null +++ b/browser/base/content/test/general/browser_documentnavigation.js @@ -0,0 +1,266 @@ +/* + * This test checks that focus is adjusted properly in a browser when pressing F6 and Shift+F6. + * There are additional tests in dom/tests/mochitest/chrome/test_focus_docnav.xul which test + * non-browser cases. + */ + +var testPage1 = "data:text/html,<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>"; +var testPage2 = "data:text/html,<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>"; +var testPage3 = "data:text/html,<html id='html3'><body id='body3' contenteditable='true'><button id='button3'>Tab 3</button></body></html>"; + +var fm = Services.focus; + +function* expectFocusOnF6(backward, expectedDocument, expectedElement, onContent, desc) +{ + let focusChangedInChildResolver = null; + let focusPromise = onContent ? new Promise(resolve => focusChangedInChildResolver = resolve) : + BrowserTestUtils.waitForEvent(window, "focus", true); + + function focusChangedListener(msg) { + let expected = expectedDocument; + if (!expectedElement.startsWith("html")) { + expected += "," + expectedElement; + } + + is(msg.data.details, expected, desc + " child focus matches"); + focusChangedInChildResolver(); + } + + if (onContent) { + messageManager.addMessageListener("BrowserTest:FocusChanged", focusChangedListener); + + yield ContentTask.spawn(gBrowser.selectedBrowser, { expectedElementId: expectedElement }, function* (arg) { + let contentExpectedElement = content.document.getElementById(arg.expectedElementId); + if (!contentExpectedElement) { + // Element not found, so look in the child frames. + for (let f = 0; f < content.frames.length; f++) { + if (content.frames[f].document.getElementById(arg.expectedElementId)) { + contentExpectedElement = content.frames[f].document; + break; + } + } + } + else if (contentExpectedElement.localName == "html") { + contentExpectedElement = contentExpectedElement.ownerDocument; + } + + if (!contentExpectedElement) { + sendSyncMessage("BrowserTest:FocusChanged", + { details : "expected element " + arg.expectedElementId + " not found" }); + return; + } + + contentExpectedElement.addEventListener("focus", function focusReceived() { + contentExpectedElement.removeEventListener("focus", focusReceived, true); + + const contentFM = Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager); + let details = contentFM.focusedWindow.document.documentElement.id; + if (contentFM.focusedElement) { + details += "," + contentFM.focusedElement.id; + } + + sendSyncMessage("BrowserTest:FocusChanged", { details : details }); + }, true); + }); + } + + EventUtils.synthesizeKey("VK_F6", { shiftKey: backward }); + yield focusPromise; + + if (typeof expectedElement == "string") { + expectedElement = fm.focusedWindow.document.getElementById(expectedElement); + } + + if (gMultiProcessBrowser && onContent) { + expectedDocument = "main-window"; + expectedElement = gBrowser.selectedBrowser; + } + + is(fm.focusedWindow.document.documentElement.id, expectedDocument, desc + " document matches"); + is(fm.focusedElement, expectedElement, desc + " element matches"); + + if (onContent) { + messageManager.removeMessageListener("BrowserTest:FocusChanged", focusChangedListener); + } +} + +// Load a page and navigate between it and the chrome window. +add_task(function* () +{ + let page1Promise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.selectedBrowser.loadURI(testPage1); + yield page1Promise; + + // When the urlbar is focused, pressing F6 should focus the root of the content page. + gURLBar.focus(); + yield* expectFocusOnF6(false, "html1", "html1", + true, "basic focus content page"); + + // When the content is focused, pressing F6 should focus the urlbar. + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "basic focus content page urlbar"); + + // When a button in content is focused, pressing F6 should focus the urlbar. + yield* expectFocusOnF6(false, "html1", "html1", + true, "basic focus content page with button focused"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { + return content.document.getElementById("button1").focus(); + }); + + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "basic focus content page with button focused urlbar"); + + // The document root should be focused, not the button + yield* expectFocusOnF6(false, "html1", "html1", + true, "basic focus again content page with button focused"); + + // Check to ensure that the root element is focused + yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { + Assert.ok(content.document.activeElement == content.document.documentElement, + "basic focus again content page with button focused child root is focused"); + }); +}); + +// Open a second tab. Document focus should skip the background tab. +add_task(function* () +{ + yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2); + + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "basic focus content page and second tab urlbar"); + yield* expectFocusOnF6(false, "html2", "html2", + true, "basic focus content page with second tab"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Shift+F6 should navigate backwards. There's only one document here so the effect +// is the same. +add_task(function* () +{ + gURLBar.focus(); + yield* expectFocusOnF6(true, "html1", "html1", + true, "back focus content page"); + yield* expectFocusOnF6(true, "main-window", gURLBar.inputField, + false, "back focus content page urlbar"); +}); + +// Open the sidebar and navigate between the sidebar, content and top-level window +add_task(function* () +{ + let sidebar = document.getElementById("sidebar"); + + let loadPromise = BrowserTestUtils.waitForEvent(sidebar, "load", true); + SidebarUI.toggle('viewBookmarksSidebar'); + yield loadPromise; + + + gURLBar.focus(); + yield* expectFocusOnF6(false, "bookmarksPanel", + sidebar.contentDocument.getElementById("search-box").inputField, + false, "focus with sidebar open sidebar"); + yield* expectFocusOnF6(false, "html1", "html1", + true, "focus with sidebar open content"); + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "focus with sidebar urlbar"); + + // Now go backwards + yield* expectFocusOnF6(true, "html1", "html1", + true, "back focus with sidebar open content"); + yield* expectFocusOnF6(true, "bookmarksPanel", + sidebar.contentDocument.getElementById("search-box").inputField, + false, "back focus with sidebar open sidebar"); + yield* expectFocusOnF6(true, "main-window", gURLBar.inputField, + false, "back focus with sidebar urlbar"); + + SidebarUI.toggle('viewBookmarksSidebar'); +}); + +// Navigate when the downloads panel is open +add_task(function* () +{ + yield pushPrefs(["accessibility.tabfocus", 7]); + + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", true); + EventUtils.synthesizeMouseAtCenter(document.getElementById("downloads-button"), { }); + yield popupShownPromise; + + gURLBar.focus(); + yield* expectFocusOnF6(false, "main-window", document.getElementById("downloadsHistory"), + false, "focus with downloads panel open panel"); + yield* expectFocusOnF6(false, "html1", "html1", + true, "focus with downloads panel open"); + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "focus downloads panel open urlbar"); + + // Now go backwards + yield* expectFocusOnF6(true, "html1", "html1", + true, "back focus with downloads panel open"); + yield* expectFocusOnF6(true, "main-window", document.getElementById("downloadsHistory"), + false, "back focus with downloads panel open"); + yield* expectFocusOnF6(true, "main-window", gURLBar.inputField, + false, "back focus downloads panel open urlbar"); + + let downloadsPopup = document.getElementById("downloadsPanel"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(downloadsPopup, "popuphidden", true); + downloadsPopup.hidePopup(); + yield popupHiddenPromise; +}); + +// Navigation with a contenteditable body +add_task(function* () +{ + yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3); + + // The body should be focused when it is editable, not the root. + gURLBar.focus(); + yield* expectFocusOnF6(false, "html3", "body3", + true, "focus with contenteditable body"); + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "focus with contenteditable body urlbar"); + + // Now go backwards + + yield* expectFocusOnF6(false, "html3", "body3", + true, "back focus with contenteditable body"); + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "back focus with contenteditable body urlbar"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Navigation with a frameset loaded +add_task(function* () +{ + yield BrowserTestUtils.openNewForegroundTab(gBrowser, + "http://mochi.test:8888/browser/browser/base/content/test/general/file_documentnavigation_frameset.html"); + + gURLBar.focus(); + yield* expectFocusOnF6(false, "htmlframe1", "htmlframe1", + true, "focus on frameset frame 0"); + yield* expectFocusOnF6(false, "htmlframe2", "htmlframe2", + true, "focus on frameset frame 1"); + yield* expectFocusOnF6(false, "htmlframe3", "htmlframe3", + true, "focus on frameset frame 2"); + yield* expectFocusOnF6(false, "htmlframe4", "htmlframe4", + true, "focus on frameset frame 3"); + yield* expectFocusOnF6(false, "main-window", gURLBar.inputField, + false, "focus on frameset frame urlbar"); + + yield* expectFocusOnF6(true, "htmlframe4", "htmlframe4", + true, "back focus on frameset frame 3"); + yield* expectFocusOnF6(true, "htmlframe3", "htmlframe3", + true, "back focus on frameset frame 2"); + yield* expectFocusOnF6(true, "htmlframe2", "htmlframe2", + true, "back focus on frameset frame 1"); + yield* expectFocusOnF6(true, "htmlframe1", "htmlframe1", + true, "back focus on frameset frame 0"); + yield* expectFocusOnF6(true, "main-window", gURLBar.inputField, + false, "back focus on frameset frame urlbar"); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// XXXndeakin add tests for browsers inside of panels diff --git a/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js new file mode 100644 index 000000000..054fb3cc0 --- /dev/null +++ b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js @@ -0,0 +1,221 @@ +"use strict"; + +var gMessageManager; + +function frameScript() { + addMessageListener("Test:RequestFullscreen", () => { + content.document.body.requestFullscreen(); + }); + addMessageListener("Test:ExitFullscreen", () => { + content.document.exitFullscreen(); + }); + addMessageListener("Test:QueryFullscreenState", () => { + sendAsyncMessage("Test:FullscreenState", { + inDOMFullscreen: !!content.document.fullscreenElement, + inFullscreen: content.fullScreen + }); + }); + content.document.addEventListener("fullscreenchange", () => { + sendAsyncMessage("Test:FullscreenChanged", { + inDOMFullscreen: !!content.document.fullscreenElement, + inFullscreen: content.fullScreen + }); + }); + function waitUntilActive() { + let doc = content.document; + if (doc.docShell.isActive && doc.hasFocus()) { + sendAsyncMessage("Test:Activated"); + } else { + setTimeout(waitUntilActive, 10); + } + } + waitUntilActive(); +} + +function listenOneMessage(aMsg, aListener) { + function listener({ data }) { + gMessageManager.removeMessageListener(aMsg, listener); + aListener(data); + } + gMessageManager.addMessageListener(aMsg, listener); +} + +function listenOneEvent(aEvent, aListener) { + function listener(evt) { + removeEventListener(aEvent, listener); + aListener(evt); + } + addEventListener(aEvent, listener); +} + +function queryFullscreenState() { + return new Promise(resolve => { + listenOneMessage("Test:FullscreenState", resolve); + gMessageManager.sendAsyncMessage("Test:QueryFullscreenState"); + }); +} + +function captureUnexpectedFullscreenChange() { + ok(false, "catched an unexpected fullscreen change"); +} + +const FS_CHANGE_DOM = 1 << 0; +const FS_CHANGE_SIZE = 1 << 1; +const FS_CHANGE_BOTH = FS_CHANGE_DOM | FS_CHANGE_SIZE; + +function waitForFullscreenChanges(aFlags) { + return new Promise(resolve => { + let fullscreenData = null; + let sizemodeChanged = false; + function tryResolve() { + if ((!(aFlags & FS_CHANGE_DOM) || fullscreenData) && + (!(aFlags & FS_CHANGE_SIZE) || sizemodeChanged)) { + if (!fullscreenData) { + queryFullscreenState().then(resolve); + } else { + resolve(fullscreenData); + } + } + } + if (aFlags & FS_CHANGE_SIZE) { + listenOneEvent("sizemodechange", () => { + sizemodeChanged = true; + tryResolve(); + }); + } + if (aFlags & FS_CHANGE_DOM) { + gMessageManager.removeMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + listenOneMessage("Test:FullscreenChanged", data => { + gMessageManager.addMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + fullscreenData = data; + tryResolve(); + }); + } + }); +} + +var gTests = [ + { + desc: "document method", + affectsFullscreenMode: false, + exitFunc: () => { + gMessageManager.sendAsyncMessage("Test:ExitFullscreen"); + } + }, + { + desc: "escape key", + affectsFullscreenMode: false, + exitFunc: () => { + executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {})); + } + }, + { + desc: "F11 key", + affectsFullscreenMode: true, + exitFunc: function () { + executeSoon(() => EventUtils.synthesizeKey("VK_F11", {})); + } + } +]; + +function checkState(expectedStates, contentStates) { + is(contentStates.inDOMFullscreen, expectedStates.inDOMFullscreen, + "The DOM fullscreen state of the content should match"); + // TODO window.fullScreen is not updated as soon as the fullscreen + // state flips in child process, hence checking it could cause + // anonying intermittent failure. As we just want to confirm the + // fullscreen state of the browser window, we can just check the + // that on the chrome window below. + // is(contentStates.inFullscreen, expectedStates.inFullscreen, + // "The fullscreen state of the content should match"); + is(!!document.fullscreenElement, expectedStates.inDOMFullscreen, + "The DOM fullscreen state of the chrome should match"); + is(window.fullScreen, expectedStates.inFullscreen, + "The fullscreen state of the chrome should match"); +} + +const kPage = "http://example.org/browser/browser/" + + "base/content/test/general/dummy_page.html"; + +add_task(function* () { + yield pushPrefs( + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"]); + + let tab = gBrowser.addTab(kPage); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + yield waitForDocLoadComplete(); + + registerCleanupFunction(() => { + if (browser.contentWindow.fullScreen) { + BrowserFullScreen(); + } + gBrowser.removeTab(tab); + }); + + gMessageManager = browser.messageManager; + gMessageManager.loadFrameScript( + "data:,(" + frameScript.toString() + ")();", false); + gMessageManager.addMessageListener( + "Test:FullscreenChanged", captureUnexpectedFullscreenChange); + + // Wait for the document being activated, so that + // fullscreen request won't be denied. + yield new Promise(resolve => listenOneMessage("Test:Activated", resolve)); + + for (let test of gTests) { + let contentStates; + info("Testing exit DOM fullscreen via " + test.desc); + + contentStates = yield queryFullscreenState(); + checkState({inDOMFullscreen: false, inFullscreen: false}, contentStates); + + /* DOM fullscreen without fullscreen mode */ + + info("> Enter DOM fullscreen"); + gMessageManager.sendAsyncMessage("Test:RequestFullscreen"); + contentStates = yield waitForFullscreenChanges(FS_CHANGE_BOTH); + checkState({inDOMFullscreen: true, inFullscreen: true}, contentStates); + + info("> Exit DOM fullscreen"); + test.exitFunc(); + contentStates = yield waitForFullscreenChanges(FS_CHANGE_BOTH); + checkState({inDOMFullscreen: false, inFullscreen: false}, contentStates); + + /* DOM fullscreen with fullscreen mode */ + + info("> Enter fullscreen mode"); + // Need to be asynchronous because sizemodechange event could be + // dispatched synchronously, which would cause the event listener + // miss that event and wait infinitely. + executeSoon(() => BrowserFullScreen()); + contentStates = yield waitForFullscreenChanges(FS_CHANGE_SIZE); + checkState({inDOMFullscreen: false, inFullscreen: true}, contentStates); + + info("> Enter DOM fullscreen in fullscreen mode"); + gMessageManager.sendAsyncMessage("Test:RequestFullscreen"); + contentStates = yield waitForFullscreenChanges(FS_CHANGE_DOM); + checkState({inDOMFullscreen: true, inFullscreen: true}, contentStates); + + info("> Exit DOM fullscreen in fullscreen mode"); + test.exitFunc(); + contentStates = yield waitForFullscreenChanges( + test.affectsFullscreenMode ? FS_CHANGE_BOTH : FS_CHANGE_DOM); + checkState({ + inDOMFullscreen: false, + inFullscreen: !test.affectsFullscreenMode + }, contentStates); + + /* Cleanup */ + + // Exit fullscreen mode if we are still in + if (window.fullScreen) { + info("> Cleanup"); + executeSoon(() => BrowserFullScreen()); + yield waitForFullscreenChanges(FS_CHANGE_SIZE); + } + } +}); diff --git a/browser/base/content/test/general/browser_double_close_tab.js b/browser/base/content/test/general/browser_double_close_tab.js new file mode 100644 index 000000000..29242c3f9 --- /dev/null +++ b/browser/base/content/test/general/browser_double_close_tab.js @@ -0,0 +1,80 @@ +"use strict"; +const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html"; +var testTab; + +SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]}); + +function waitForDialog(callback) { + function onTabModalDialogLoaded(node) { + Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded"); + callback(node); + } + + // Listen for the dialog being created + Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false); +} + +function waitForDialogDestroyed(node, callback) { + // Now listen for the dialog going away again... + let observer = new MutationObserver(function(muts) { + if (!node.parentNode) { + ok(true, "Dialog is gone"); + done(); + } + }); + observer.observe(node.parentNode, {childList: true}); + let failureTimeout = setTimeout(function() { + ok(false, "Dialog should have been destroyed"); + done(); + }, 10000); + + function done() { + clearTimeout(failureTimeout); + observer.disconnect(); + observer = null; + callback(); + } +} + +add_task(function*() { + testTab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(testTab, TEST_PAGE); + // XXXgijs the reason this has nesting and callbacks rather than promises is + // that DOM promises resolve on the next tick. So they're scheduled + // in an event queue. So when we spin a new event queue for a modal dialog... + // everything gets messed up and the promise's .then callbacks never get + // called, despite resolve() being called just fine. + yield new Promise(resolveOuter => { + waitForDialog(dialogNode => { + waitForDialogDestroyed(dialogNode, () => { + let doCompletion = () => setTimeout(resolveOuter, 0); + info("Now checking if dialog is destroyed"); + ok(!dialogNode.parentNode, "onbeforeunload dialog should be gone."); + if (dialogNode.parentNode) { + // Failed to remove onbeforeunload dialog, so do it ourselves: + let leaveBtn = dialogNode.ui.button0; + waitForDialogDestroyed(dialogNode, doCompletion); + EventUtils.synthesizeMouseAtCenter(leaveBtn, {}); + return; + } + doCompletion(); + }); + // Click again: + document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click(); + }); + // Click once: + document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click(); + }); + yield promiseWaitForCondition(() => !testTab.parentNode); + ok(!testTab.parentNode, "Tab should be closed completely"); +}); + +registerCleanupFunction(function() { + if (testTab.parentNode) { + // Remove the handler, or closing this tab will prove tricky: + try { + testTab.linkedBrowser.contentWindow.onbeforeunload = null; + } catch (ex) {} + gBrowser.removeTab(testTab); + } +}); diff --git a/browser/base/content/test/general/browser_drag.js b/browser/base/content/test/general/browser_drag.js new file mode 100644 index 000000000..64ad19bde --- /dev/null +++ b/browser/base/content/test/general/browser_drag.js @@ -0,0 +1,45 @@ +function test() +{ + waitForExplicitFinish(); + + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + // ---- Test dragging the proxy icon --- + var value = content.location.href; + var urlString = value + "\n" + content.document.title; + var htmlString = "<a href=\"" + value + "\">" + value + "</a>"; + var expected = [ [ + { type : "text/x-moz-url", + data : urlString }, + { type : "text/uri-list", + data : value }, + { type : "text/plain", + data : value }, + { type : "text/html", + data : htmlString } + ] ]; + // set the valid attribute so dropping is allowed + var oldstate = gURLBar.getAttribute("pageproxystate"); + gURLBar.setAttribute("pageproxystate", "valid"); + var dt = EventUtils.synthesizeDragStart(document.getElementById("identity-box"), expected); + is(dt, null, "drag on proxy icon"); + gURLBar.setAttribute("pageproxystate", oldstate); + // Now, the identity information panel is opened by the proxy icon click. + // We need to close it for next tests. + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + + // now test dragging onto a tab + var tab = gBrowser.addTab("about:blank", {skipAnimation: true}); + var browser = gBrowser.getBrowserForTab(tab); + + browser.addEventListener("load", function () { + is(browser.contentWindow.location, "http://mochi.test:8888/", "drop on tab"); + gBrowser.removeTab(tab); + finish(); + }, true); + + EventUtils.synthesizeDrop(tab, tab, [[{type: "text/uri-list", data: "http://mochi.test:8888/"}]], "copy", window); +} diff --git a/browser/base/content/test/general/browser_duplicateIDs.js b/browser/base/content/test/general/browser_duplicateIDs.js new file mode 100644 index 000000000..38fc17820 --- /dev/null +++ b/browser/base/content/test/general/browser_duplicateIDs.js @@ -0,0 +1,8 @@ +function test() { + var ids = {}; + Array.forEach(document.querySelectorAll("[id]"), function (node) { + var id = node.id; + ok(!(id in ids), id + " should be unique"); + ids[id] = null; + }); +} diff --git a/browser/base/content/test/general/browser_e10s_about_process.js b/browser/base/content/test/general/browser_e10s_about_process.js new file mode 100644 index 000000000..2b4816754 --- /dev/null +++ b/browser/base/content/test/general/browser_e10s_about_process.js @@ -0,0 +1,114 @@ +const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + +const CHROME = { + id: "cb34538a-d9da-40f3-b61a-069f0b2cb9fb", + path: "test-chrome", + flags: 0, +} +const CANREMOTE = { + id: "2480d3e1-9ce4-4b84-8ae3-910b9a95cbb3", + path: "test-allowremote", + flags: Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD, +} +const MUSTREMOTE = { + id: "f849cee5-e13e-44d2-981d-0fb3884aaead", + path: "test-mustremote", + flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD, +} + +const TEST_MODULES = [ + CHROME, + CANREMOTE, + MUSTREMOTE +] + +function AboutModule() { +} + +AboutModule.prototype = { + newChannel: function(aURI, aLoadInfo) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + getURIFlags: function(aURI) { + for (let module of TEST_MODULES) { + if (aURI.path.startsWith(module.path)) { + return module.flags; + } + } + + ok(false, "Called getURIFlags for an unknown page " + aURI.spec); + return 0; + }, + + getIndexedDBOriginPostfix: function(aURI) { + return null; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]) +}; + +var AboutModuleFactory = { + createInstance: function(aOuter, aIID) { + if (aOuter) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return new AboutModule().QueryInterface(aIID); + }, + + lockFactory: function(aLock) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) +}; + +add_task(function* init() { + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + for (let module of TEST_MODULES) { + registrar.registerFactory(Components.ID(module.id), "", + "@mozilla.org/network/protocol/about;1?what=" + module.path, + AboutModuleFactory); + } +}); + +registerCleanupFunction(() => { + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + for (let module of TEST_MODULES) { + registrar.unregisterFactory(Components.ID(module.id), AboutModuleFactory); + } +}); + +function test_url(url, chromeResult, contentResult) { + is(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS), + chromeResult, "Check URL in chrome process."); + is(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS), + contentResult, "Check URL in content process."); + + is(E10SUtils.canLoadURIInProcess(url + "#foo", CHROME_PROCESS), + chromeResult, "Check URL with ref in chrome process."); + is(E10SUtils.canLoadURIInProcess(url + "#foo", CONTENT_PROCESS), + contentResult, "Check URL with ref in content process."); + + is(E10SUtils.canLoadURIInProcess(url + "?foo", CHROME_PROCESS), + chromeResult, "Check URL with query in chrome process."); + is(E10SUtils.canLoadURIInProcess(url + "?foo", CONTENT_PROCESS), + contentResult, "Check URL with query in content process."); + + is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CHROME_PROCESS), + chromeResult, "Check URL with query and ref in chrome process."); + is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CONTENT_PROCESS), + contentResult, "Check URL with query and ref in content process."); +} + +add_task(function* test_chrome() { + test_url("about:" + CHROME.path, true, false); +}); + +add_task(function* test_any() { + test_url("about:" + CANREMOTE.path, true, true); +}); + +add_task(function* test_remote() { + test_url("about:" + MUSTREMOTE.path, false, true); +}); diff --git a/browser/base/content/test/general/browser_e10s_chrome_process.js b/browser/base/content/test/general/browser_e10s_chrome_process.js new file mode 100644 index 000000000..0726447ce --- /dev/null +++ b/browser/base/content/test/general/browser_e10s_chrome_process.js @@ -0,0 +1,150 @@ +// Returns a function suitable for add_task which loads startURL, runs +// transitionTask and waits for endURL to load, checking that the URLs were +// loaded in the correct process. +function makeTest(name, startURL, startProcessIsRemote, endURL, endProcessIsRemote, transitionTask) { + return function*() { + info("Running test " + name + ", " + transitionTask.name); + let browser = gBrowser.selectedBrowser; + + // In non-e10s nothing should be remote + if (!gMultiProcessBrowser) { + startProcessIsRemote = false; + endProcessIsRemote = false; + } + + // Load the initial URL and make sure we are in the right initial process + info("Loading initial URL"); + browser.loadURI(startURL); + yield waitForDocLoadComplete(); + + is(browser.currentURI.spec, startURL, "Shouldn't have been redirected"); + is(browser.isRemoteBrowser, startProcessIsRemote, "Should be displayed in the right process"); + + let docLoadedPromise = waitForDocLoadComplete(); + let asyncTask = Task.async(transitionTask); + let expectSyncChange = yield asyncTask(browser, endURL); + if (expectSyncChange) { + is(browser.isRemoteBrowser, endProcessIsRemote, "Should have switched to the right process synchronously"); + } + yield docLoadedPromise; + + is(browser.currentURI.spec, endURL, "Should have made it to the final URL"); + is(browser.isRemoteBrowser, endProcessIsRemote, "Should be displayed in the right process"); + } +} + +const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; +const PATH = (getRootDirectory(gTestPath) + "test_process_flags_chrome.html").replace("chrome://mochitests", ""); + +const CHROME = "chrome://mochitests" + PATH; +const CANREMOTE = "chrome://mochitests-any" + PATH; +const MUSTREMOTE = "chrome://mochitests-content" + PATH; + +add_task(function* init() { + gBrowser.selectedTab = gBrowser.addTab("about:blank"); +}); + +registerCleanupFunction(() => { + gBrowser.removeCurrentTab(); +}); + +function test_url(url, chromeResult, contentResult) { + is(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS), + chromeResult, "Check URL in chrome process."); + is(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS), + contentResult, "Check URL in content process."); + + is(E10SUtils.canLoadURIInProcess(url + "#foo", CHROME_PROCESS), + chromeResult, "Check URL with ref in chrome process."); + is(E10SUtils.canLoadURIInProcess(url + "#foo", CONTENT_PROCESS), + contentResult, "Check URL with ref in content process."); + + is(E10SUtils.canLoadURIInProcess(url + "?foo", CHROME_PROCESS), + chromeResult, "Check URL with query in chrome process."); + is(E10SUtils.canLoadURIInProcess(url + "?foo", CONTENT_PROCESS), + contentResult, "Check URL with query in content process."); + + is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CHROME_PROCESS), + chromeResult, "Check URL with query and ref in chrome process."); + is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CONTENT_PROCESS), + contentResult, "Check URL with query and ref in content process."); +} + +add_task(function* test_chrome() { + test_url(CHROME, true, false); +}); + +add_task(function* test_any() { + test_url(CANREMOTE, true, true); +}); + +add_task(function* test_remote() { + test_url(MUSTREMOTE, false, true); +}); + +// The set of page transitions +var TESTS = [ + [ + "chrome -> chrome", + CHROME, false, + CHROME, false, + ], + [ + "chrome -> canremote", + CHROME, false, + CANREMOTE, false, + ], + [ + "chrome -> mustremote", + CHROME, false, + MUSTREMOTE, true, + ], + [ + "remote -> chrome", + MUSTREMOTE, true, + CHROME, false, + ], + [ + "remote -> canremote", + MUSTREMOTE, true, + CANREMOTE, true, + ], + [ + "remote -> mustremote", + MUSTREMOTE, true, + MUSTREMOTE, true, + ], +]; + +// The different ways to transition from one page to another +var TRANSITIONS = [ +// Loads the new page by calling browser.loadURI directly +function* loadURI(browser, uri) { + info("Calling browser.loadURI"); + yield BrowserTestUtils.loadURI(browser, uri); + return true; +}, + +// Loads the new page by finding a link with the right href in the document and +// clicking it +function* clickLink(browser, uri) { + info("Clicking link"); + + function frame_script(frameUri) { + let link = content.document.querySelector("a[href='" + frameUri + "']"); + link.click(); + } + + browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")(" + JSON.stringify(uri) + ");", false); + + return false; +}, +]; + +// Creates a set of test tasks, one for each combination of TESTS and TRANSITIONS. +for (let test of TESTS) { + for (let transition of TRANSITIONS) { + add_task(makeTest(...test, transition)); + } +} diff --git a/browser/base/content/test/general/browser_e10s_javascript.js b/browser/base/content/test/general/browser_e10s_javascript.js new file mode 100644 index 000000000..90e847b09 --- /dev/null +++ b/browser/base/content/test/general/browser_e10s_javascript.js @@ -0,0 +1,11 @@ +const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + +add_task(function*() { + let url = "javascript:dosomething()"; + + ok(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS), + "Check URL in chrome process."); + ok(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS), + "Check URL in content process."); +}); diff --git a/browser/base/content/test/general/browser_e10s_switchbrowser.js b/browser/base/content/test/general/browser_e10s_switchbrowser.js new file mode 100644 index 000000000..e6134f749 --- /dev/null +++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js @@ -0,0 +1,261 @@ +requestLongerTimeout(2); + +const DUMMY_PATH = "browser/browser/base/content/test/general/dummy_page.html"; + +const gExpectedHistory = { + index: -1, + entries: [] +}; + +function get_remote_history(browser) { + function frame_script() { + let webNav = docShell.QueryInterface(Components.interfaces.nsIWebNavigation); + let sessionHistory = webNav.sessionHistory; + let result = { + index: sessionHistory.index, + entries: [] + }; + + for (let i = 0; i < sessionHistory.count; i++) { + let entry = sessionHistory.getEntryAtIndex(i, false); + result.entries.push({ + uri: entry.URI.spec, + title: entry.title + }); + } + + sendAsyncMessage("Test:History", result); + } + + return new Promise(resolve => { + browser.messageManager.addMessageListener("Test:History", function listener({data}) { + browser.messageManager.removeMessageListener("Test:History", listener); + resolve(data); + }); + + browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", true); + }); +} + +var check_history = Task.async(function*() { + let sessionHistory = yield get_remote_history(gBrowser.selectedBrowser); + + let count = sessionHistory.entries.length; + is(count, gExpectedHistory.entries.length, "Should have the right number of history entries"); + is(sessionHistory.index, gExpectedHistory.index, "Should have the right history index"); + + for (let i = 0; i < count; i++) { + let entry = sessionHistory.entries[i]; + is(entry.uri, gExpectedHistory.entries[i].uri, "Should have the right URI"); + is(entry.title, gExpectedHistory.entries[i].title, "Should have the right title"); + } +}); + +function clear_history() { + gExpectedHistory.index = -1; + gExpectedHistory.entries = []; +} + +// Waits for a load and updates the known history +var waitForLoad = Task.async(function*(uri) { + info("Loading " + uri); + // Longwinded but this ensures we don't just shortcut to LoadInNewProcess + gBrowser.selectedBrowser.webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null); + + yield waitForDocLoadComplete(); + gExpectedHistory.index++; + gExpectedHistory.entries.push({ + uri: gBrowser.currentURI.spec, + title: gBrowser.contentTitle + }); +}); + +// Waits for a load and updates the known history +var waitForLoadWithFlags = Task.async(function*(uri, flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE) { + info("Loading " + uri + " flags = " + flags); + gBrowser.selectedBrowser.loadURIWithFlags(uri, flags, null, null, null); + + yield waitForDocLoadComplete(); + if (!(flags & Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY)) { + + if (flags & Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY) { + gExpectedHistory.entries.pop(); + } + else { + gExpectedHistory.index++; + } + + gExpectedHistory.entries.push({ + uri: gBrowser.currentURI.spec, + title: gBrowser.contentTitle + }); + } +}); + +var back = Task.async(function*() { + info("Going back"); + gBrowser.goBack(); + yield waitForDocLoadComplete(); + gExpectedHistory.index--; +}); + +var forward = Task.async(function*() { + info("Going forward"); + gBrowser.goForward(); + yield waitForDocLoadComplete(); + gExpectedHistory.index++; +}); + +// Tests that navigating from a page that should be in the remote process and +// a page that should be in the main process works and retains history +add_task(function* test_navigation() { + let expectedRemote = gMultiProcessBrowser; + + info("1"); + // Create a tab and load a remote page in it + gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true}); + let {permanentKey} = gBrowser.selectedBrowser; + yield waitForLoad("http://example.org/" + DUMMY_PATH); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + + info("2"); + // Load another page + yield waitForLoad("http://example.com/" + DUMMY_PATH); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("3"); + // Load a non-remote page + yield waitForLoad("about:robots"); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("4"); + // Load a remote page + yield waitForLoad("http://example.org/" + DUMMY_PATH); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("5"); + yield back(); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("6"); + yield back(); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("7"); + yield forward(); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("8"); + yield forward(); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("9"); + yield back(); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("10"); + // Load a new remote page, this should replace the last history entry + gExpectedHistory.entries.splice(gExpectedHistory.entries.length - 1, 1); + yield waitForLoad("http://example.com/" + DUMMY_PATH); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + yield check_history(); + + info("11"); + gBrowser.removeCurrentTab(); + clear_history(); +}); + +// Tests that calling gBrowser.loadURI or browser.loadURI to load a page in a +// different process updates the browser synchronously +add_task(function* test_synchronous() { + let expectedRemote = gMultiProcessBrowser; + + info("1"); + // Create a tab and load a remote page in it + gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true}); + let {permanentKey} = gBrowser.selectedBrowser; + yield waitForLoad("http://example.org/" + DUMMY_PATH); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + + info("2"); + // Load another page + info("Loading about:robots"); + yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:robots"); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + + yield waitForDocLoadComplete(); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + + info("3"); + // Load the remote page again + info("Loading http://example.org/" + DUMMY_PATH); + yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "http://example.org/" + DUMMY_PATH); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + + yield waitForDocLoadComplete(); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same"); + + info("4"); + gBrowser.removeCurrentTab(); + clear_history(); +}); + +// Tests that load flags are correctly passed through to the child process with +// normal loads +add_task(function* test_loadflags() { + let expectedRemote = gMultiProcessBrowser; + + info("1"); + // Create a tab and load a remote page in it + gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true}); + yield waitForLoadWithFlags("about:robots"); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + yield check_history(); + + info("2"); + // Load a page in the remote process with some custom flags + yield waitForLoadWithFlags("http://example.com/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + yield check_history(); + + info("3"); + // Load a non-remote page + yield waitForLoadWithFlags("about:robots"); + is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct"); + yield check_history(); + + info("4"); + // Load another remote page + yield waitForLoadWithFlags("http://example.org/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY); + is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct"); + yield check_history(); + + is(gExpectedHistory.entries.length, 2, "Should end with the right number of history entries"); + + info("5"); + gBrowser.removeCurrentTab(); + clear_history(); +}); diff --git a/browser/base/content/test/general/browser_favicon_change.js b/browser/base/content/test/general/browser_favicon_change.js new file mode 100644 index 000000000..f6b0a2a42 --- /dev/null +++ b/browser/base/content/test/general/browser_favicon_change.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change.html" + +add_task(function*() { + let extraTab = gBrowser.selectedTab = gBrowser.addTab(); + extraTab.linkedBrowser.loadURI(TEST_URL); + let tabLoaded = BrowserTestUtils.browserLoaded(extraTab.linkedBrowser); + let expectedFavicon = "http://example.org/one-icon"; + let haveChanged = new Promise.defer(); + let observer = new MutationObserver(function(mutations) { + for (let mut of mutations) { + if (mut.attributeName != "image") { + continue; + } + let imageVal = extraTab.getAttribute("image").replace(/#.*$/, ""); + if (!imageVal) { + // The value gets removed because it doesn't load. + continue; + } + is(imageVal, expectedFavicon, "Favicon image should correspond to expected image."); + haveChanged.resolve(); + } + }); + observer.observe(extraTab, {attributes: true}); + yield tabLoaded; + yield haveChanged.promise; + haveChanged = new Promise.defer(); + expectedFavicon = "http://example.org/other-icon"; + ContentTask.spawn(extraTab.linkedBrowser, null, function() { + let ev = new content.CustomEvent("PleaseChangeFavicon", {}); + content.dispatchEvent(ev); + }); + yield haveChanged.promise; + observer.disconnect(); + gBrowser.removeTab(extraTab); +}); + diff --git a/browser/base/content/test/general/browser_favicon_change_not_in_document.js b/browser/base/content/test/general/browser_favicon_change_not_in_document.js new file mode 100644 index 000000000..d14a1da32 --- /dev/null +++ b/browser/base/content/test/general/browser_favicon_change_not_in_document.js @@ -0,0 +1,34 @@ +"use strict"; + +const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change_not_in_document.html" + +add_task(function*() { + let extraTab = gBrowser.selectedTab = gBrowser.addTab(); + let tabLoaded = promiseTabLoaded(extraTab); + extraTab.linkedBrowser.loadURI(TEST_URL); + let expectedFavicon = "http://example.org/one-icon"; + let haveChanged = new Promise.defer(); + let observer = new MutationObserver(function(mutations) { + for (let mut of mutations) { + if (mut.attributeName != "image") { + continue; + } + let imageVal = extraTab.getAttribute("image").replace(/#.*$/, ""); + if (!imageVal) { + // The value gets removed because it doesn't load. + continue; + } + is(imageVal, expectedFavicon, "Favicon image should correspond to expected image."); + haveChanged.resolve(); + } + }); + observer.observe(extraTab, {attributes: true}); + yield tabLoaded; + expectedFavicon = "http://example.org/yet-another-icon"; + haveChanged = new Promise.defer(); + yield haveChanged.promise; + observer.disconnect(); + gBrowser.removeTab(extraTab); +}); + + diff --git a/browser/base/content/test/general/browser_feed_discovery.js b/browser/base/content/test/general/browser_feed_discovery.js new file mode 100644 index 000000000..73dcef755 --- /dev/null +++ b/browser/base/content/test/general/browser_feed_discovery.js @@ -0,0 +1,33 @@ +const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/feed_discovery.html" + +/** Test for Bug 377611 **/ + +add_task(function* () { + // Open a new tab. + gBrowser.selectedTab = gBrowser.addTab(URL); + registerCleanupFunction(() => gBrowser.removeCurrentTab()); + + let browser = gBrowser.selectedBrowser; + yield BrowserTestUtils.browserLoaded(browser); + + let discovered = browser.feeds; + ok(discovered.length > 0, "some feeds should be discovered"); + + let feeds = {}; + for (let aFeed of discovered) { + feeds[aFeed.href] = true; + } + + yield ContentTask.spawn(browser, feeds, function* (contentFeeds) { + for (let aLink of content.document.getElementsByTagName("link")) { + // ignore real stylesheets, and anything without an href property + if (aLink.type != "text/css" && aLink.href) { + if (/bogus/i.test(aLink.title)) { + ok(!contentFeeds[aLink.href], "don't discover " + aLink.href); + } else { + ok(contentFeeds[aLink.href], "should discover " + aLink.href); + } + } + } + }); +}) diff --git a/browser/base/content/test/general/browser_findbarClose.js b/browser/base/content/test/general/browser_findbarClose.js new file mode 100644 index 000000000..53503073c --- /dev/null +++ b/browser/base/content/test/general/browser_findbarClose.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests find bar auto-close behavior + +var newTab; + +add_task(function* findbar_test() { + waitForExplicitFinish(); + newTab = gBrowser.addTab("about:blank"); + + let promise = ContentTask.spawn(newTab.linkedBrowser, null, function* () { + yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", false); + }); + newTab.linkedBrowser.loadURI("http://example.com/browser/" + + "browser/base/content/test/general/test_bug628179.html"); + yield promise; + + gFindBar.open(); + + yield new ContentTask.spawn(newTab.linkedBrowser, null, function* () { + let iframe = content.document.getElementById("iframe"); + let awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false); + iframe.src = "http://example.org/"; + yield awaitLoad; + }); + + ok(!gFindBar.hidden, "the Find bar isn't hidden after the location of a " + + "subdocument changes"); + + gFindBar.close(); + gBrowser.removeTab(newTab); + finish(); +}); + diff --git a/browser/base/content/test/general/browser_focusonkeydown.js b/browser/base/content/test/general/browser_focusonkeydown.js new file mode 100644 index 000000000..5b3337203 --- /dev/null +++ b/browser/base/content/test/general/browser_focusonkeydown.js @@ -0,0 +1,26 @@ +add_task(function *() +{ + let keyUps = 0; + + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,<body>"); + + gURLBar.focus(); + + window.addEventListener("keyup", function countKeyUps(event) { + window.removeEventListener("keyup", countKeyUps, true); + if (event.originalTarget == gURLBar.inputField) { + keyUps++; + } + }, true); + + gURLBar.addEventListener("keydown", function redirectFocus(event) { + gURLBar.removeEventListener("keydown", redirectFocus, true); + gBrowser.selectedBrowser.focus(); + }, true); + + EventUtils.synthesizeKey("v", { }); + + is(keyUps, 1, "Key up fired at url bar"); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_fullscreen-window-open.js b/browser/base/content/test/general/browser_fullscreen-window-open.js new file mode 100644 index 000000000..2624b754a --- /dev/null +++ b/browser/base/content/test/general/browser_fullscreen-window-open.js @@ -0,0 +1,347 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +const PREF_DISABLE_OPEN_NEW_WINDOW = "browser.link.open_newwindow.disabled_in_fullscreen"; +const isOSX = (Services.appinfo.OS === "Darwin"); + +const TEST_FILE = "file_fullscreen-window-open.html"; +const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", + "http://127.0.0.1:8888/"); + +function test () { + waitForExplicitFinish(); + + Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true); + + let newTab = gBrowser.addTab(gHttpTestRoot + TEST_FILE); + gBrowser.selectedTab = newTab; + + whenTabLoaded(newTab, function () { + // Enter browser fullscreen mode. + BrowserFullScreen(); + + runNextTest(); + }); +} + +registerCleanupFunction(function() { + // Exit browser fullscreen mode. + BrowserFullScreen(); + + gBrowser.removeCurrentTab(); + + Services.prefs.clearUserPref(PREF_DISABLE_OPEN_NEW_WINDOW); +}); + +var gTests = [ + test_open, + test_open_with_size, + test_open_with_pos, + test_open_with_outerSize, + test_open_with_innerSize, + test_open_with_dialog, + test_open_when_open_new_window_by_pref, + test_open_with_pref_to_disable_in_fullscreen, + test_open_from_chrome, +]; + +function runNextTest () { + let testCase = gTests.shift(); + if (testCase) { + executeSoon(testCase); + } + else { + finish(); + } +} + + +// Test for window.open() with no feature. +function test_open() { + waitForTabOpen({ + message: { + title: "test_open", + param: "", + }, + finalizeFn: function () {}, + }); +} + +// Test for window.open() with width/height. +function test_open_with_size() { + waitForTabOpen({ + message: { + title: "test_open_with_size", + param: "width=400,height=400", + }, + finalizeFn: function () {}, + }); +} + +// Test for window.open() with top/left. +function test_open_with_pos() { + waitForTabOpen({ + message: { + title: "test_open_with_pos", + param: "top=200,left=200", + }, + finalizeFn: function () {}, + }); +} + +// Test for window.open() with outerWidth/Height. +function test_open_with_outerSize() { + let [outerWidth, outerHeight] = [window.outerWidth, window.outerHeight]; + waitForTabOpen({ + message: { + title: "test_open_with_outerSize", + param: "outerWidth=200,outerHeight=200", + }, + successFn: function () { + is(window.outerWidth, outerWidth, "Don't change window.outerWidth."); + is(window.outerHeight, outerHeight, "Don't change window.outerHeight."); + }, + finalizeFn: function () {}, + }); +} + +// Test for window.open() with innerWidth/Height. +function test_open_with_innerSize() { + let [innerWidth, innerHeight] = [window.innerWidth, window.innerHeight]; + waitForTabOpen({ + message: { + title: "test_open_with_innerSize", + param: "innerWidth=200,innerHeight=200", + }, + successFn: function () { + is(window.innerWidth, innerWidth, "Don't change window.innerWidth."); + is(window.innerHeight, innerHeight, "Don't change window.innerHeight."); + }, + finalizeFn: function () {}, + }); +} + +// Test for window.open() with dialog. +function test_open_with_dialog() { + waitForTabOpen({ + message: { + title: "test_open_with_dialog", + param: "dialog=yes", + }, + finalizeFn: function () {}, + }); +} + +// Test for window.open() +// when "browser.link.open_newwindow" is nsIBrowserDOMWindow.OPEN_NEWWINDOW +function test_open_when_open_new_window_by_pref() { + const PREF_NAME = "browser.link.open_newwindow"; + Services.prefs.setIntPref(PREF_NAME, Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW); + is(Services.prefs.getIntPref(PREF_NAME), Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW, + PREF_NAME + " is nsIBrowserDOMWindow.OPEN_NEWWINDOW at this time"); + + waitForTabOpen({ + message: { + title: "test_open_when_open_new_window_by_pref", + param: "width=400,height=400", + }, + finalizeFn: function () { + Services.prefs.clearUserPref(PREF_NAME); + }, + }); +} + +// Test for the pref, "browser.link.open_newwindow.disabled_in_fullscreen" +function test_open_with_pref_to_disable_in_fullscreen() { + Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, false); + + waitForWindowOpen({ + message: { + title: "test_open_with_pref_disabled_in_fullscreen", + param: "width=400,height=400", + }, + finalizeFn: function () { + Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true); + }, + }); +} + + +// Test for window.open() called from chrome context. +function test_open_from_chrome() { + waitForWindowOpenFromChrome({ + message: { + title: "test_open_from_chrome", + param: "", + }, + finalizeFn: function () {} + }); +} + +function waitForTabOpen(aOptions) { + let message = aOptions.message; + + if (!message.title) { + ok(false, "Can't get message.title."); + aOptions.finalizeFn(); + runNextTest(); + return; + } + + info("Running test: " + message.title); + + let onTabOpen = function onTabOpen(aEvent) { + gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true); + + let tab = aEvent.target; + whenTabLoaded(tab, function () { + is(tab.linkedBrowser.contentTitle, message.title, + "Opened Tab is expected: " + message.title); + + if (aOptions.successFn) { + aOptions.successFn(); + } + + gBrowser.removeTab(tab); + finalize(); + }); + } + gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true); + + let finalize = function () { + aOptions.finalizeFn(); + info("Finished: " + message.title); + runNextTest(); + }; + + const URI = "data:text/html;charset=utf-8,<!DOCTYPE html><html><head><title>"+ + message.title + + "<%2Ftitle><%2Fhead><body><%2Fbody><%2Fhtml>"; + + executeWindowOpenInContent({ + uri: URI, + title: message.title, + option: message.param, + }); +} + + +function waitForWindowOpen(aOptions) { + let message = aOptions.message; + let url = aOptions.url || "about:blank"; + + if (!message.title) { + ok(false, "Can't get message.title"); + aOptions.finalizeFn(); + runNextTest(); + return; + } + + info("Running test: " + message.title); + + let onFinalize = function () { + aOptions.finalizeFn(); + + info("Finished: " + message.title); + runNextTest(); + }; + + let listener = new WindowListener(message.title, getBrowserURL(), { + onSuccess: aOptions.successFn, + onFinalize: onFinalize, + }); + Services.wm.addListener(listener); + + executeWindowOpenInContent({ + uri: url, + title: message.title, + option: message.param, + }); +} + +function executeWindowOpenInContent(aParam) { + ContentTask.spawn(gBrowser.selectedBrowser, JSON.stringify(aParam), function* (dataTestParam) { + let testElm = content.document.getElementById("test"); + testElm.setAttribute("data-test-param", dataTestParam); + testElm.click(); + }); +} + +function waitForWindowOpenFromChrome(aOptions) { + let message = aOptions.message; + let url = aOptions.url || "about:blank"; + + if (!message.title) { + ok(false, "Can't get message.title"); + aOptions.finalizeFn(); + runNextTest(); + return; + } + + info("Running test: " + message.title); + + let onFinalize = function () { + aOptions.finalizeFn(); + + info("Finished: " + message.title); + runNextTest(); + }; + + let listener = new WindowListener(message.title, getBrowserURL(), { + onSuccess: aOptions.successFn, + onFinalize: onFinalize, + }); + Services.wm.addListener(listener); + + window.open(url, message.title, message.option); +} + +function WindowListener(aTitle, aUrl, aCallBackObj) { + this.test_title = aTitle; + this.test_url = aUrl; + this.callback_onSuccess = aCallBackObj.onSuccess; + this.callBack_onFinalize = aCallBackObj.onFinalize; +} +WindowListener.prototype = { + + test_title: null, + test_url: null, + callback_onSuccess: null, + callBack_onFinalize: null, + + onOpenWindow: function(aXULWindow) { + Services.wm.removeListener(this); + + let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + let onLoad = aEvent => { + is(domwindow.document.location.href, this.test_url, + "Opened Window is expected: "+ this.test_title); + if (this.callback_onSuccess) { + this.callback_onSuccess(); + } + + domwindow.removeEventListener("load", onLoad, true); + + // wait for trasition to fullscreen on OSX Lion later + if (isOSX) { + setTimeout(function() { + domwindow.close(); + executeSoon(this.callBack_onFinalize); + }.bind(this), 3000); + } + else { + domwindow.close(); + executeSoon(this.callBack_onFinalize); + } + }; + domwindow.addEventListener("load", onLoad, true); + }, + onCloseWindow: function(aXULWindow) {}, + onWindowTitleChange: function(aXULWindow, aNewTitle) {}, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediatorListener, + Ci.nsISupports]), +}; diff --git a/browser/base/content/test/general/browser_fxa_migrate.js b/browser/base/content/test/general/browser_fxa_migrate.js new file mode 100644 index 000000000..2faf9fb10 --- /dev/null +++ b/browser/base/content/test/general/browser_fxa_migrate.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const STATE_CHANGED_TOPIC = "fxa-migration:state-changed"; +const NOTIFICATION_TITLE = "fxa-migration"; + +var imports = {}; +Cu.import("resource://services-sync/FxaMigrator.jsm", imports); + +add_task(function* test() { + // Fake the state where we saw an EOL notification. + Services.obs.notifyObservers(null, STATE_CHANGED_TOPIC, null); + + let notificationBox = document.getElementById("global-notificationbox"); + Assert.ok(notificationBox.allNotifications.some(n => { + return n.getAttribute("value") == NOTIFICATION_TITLE; + }), "Disconnect notification should be present"); +}); diff --git a/browser/base/content/test/general/browser_fxa_oauth.html b/browser/base/content/test/general/browser_fxa_oauth.html new file mode 100644 index 000000000..b31e7ceb4 --- /dev/null +++ b/browser/base/content/test/general/browser_fxa_oauth.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>fxa_oauth_test</title> +</head> +<body> +<script> + window.onload = function() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + // Note: This intentionally sends an object instead of a string, to ensure both work + // (see browser_fxa_oauth_with_keys.html for the other test) + detail: { + id: "oauth_client_id", + message: { + command: "oauth_complete", + data: { + state: "state", + code: "code1", + closeWindow: "signin", + }, + }, + }, + }); + + window.dispatchEvent(event); + }; +</script> +</body> +</html> diff --git a/browser/base/content/test/general/browser_fxa_oauth.js b/browser/base/content/test/general/browser_fxa_oauth.js new file mode 100644 index 000000000..1f688bfa8 --- /dev/null +++ b/browser/base/content/test/general/browser_fxa_oauth.js @@ -0,0 +1,327 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null"); + +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthClient", + "resource://gre/modules/FxAccountsOAuthClient.jsm"); + +const HTTP_PATH = "http://example.com"; +const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_fxa_oauth.html"; +const HTTP_ENDPOINT_WITH_KEYS = "/browser/browser/base/content/test/general/browser_fxa_oauth_with_keys.html"; + +var gTests = [ + { + desc: "FxA OAuth - should open a new tab, complete OAuth flow", + run: function () { + return new Promise(function(resolve, reject) { + let tabOpened = false; + let properURL = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html"; + let queryStrings = [ + "action=signin", + "client_id=client_id", + "scope=", + "state=state", + "webChannelId=oauth_client_id", + ]; + queryStrings.sort(); + + waitForTab(function (tab) { + Assert.ok("Tab successfully opened"); + Assert.ok(gBrowser.currentURI.spec.split("?")[0], properURL, "Check URL without params"); + let actualURL = new URL(gBrowser.currentURI.spec); + let actualQueryStrings = actualURL.search.substring(1).split("&"); + actualQueryStrings.sort(); + Assert.equal(actualQueryStrings.length, queryStrings.length, "Check number of params"); + + for (let i = 0; i < queryStrings.length; i++) { + Assert.equal(actualQueryStrings[i], queryStrings[i], "Check parameter " + i); + } + + tabOpened = true; + }); + + let client = new FxAccountsOAuthClient({ + parameters: { + state: "state", + client_id: "client_id", + oauth_uri: HTTP_PATH, + content_uri: HTTP_PATH, + }, + authorizationEndpoint: HTTP_ENDPOINT + }); + + client.onComplete = function(tokenData) { + Assert.ok(tabOpened); + Assert.equal(tokenData.code, "code1"); + Assert.equal(tokenData.state, "state"); + resolve(); + }; + + client.onError = reject; + + client.launchWebFlow(); + }); + } + }, + { + desc: "FxA OAuth - should open a new tab, complete OAuth flow when forcing auth", + run: function () { + return new Promise(function(resolve, reject) { + let tabOpened = false; + let properURL = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html"; + let queryStrings = [ + "action=force_auth", + "client_id=client_id", + "scope=", + "state=state", + "webChannelId=oauth_client_id", + "email=test%40invalid.com", + ]; + queryStrings.sort(); + + waitForTab(function (tab) { + Assert.ok("Tab successfully opened"); + Assert.ok(gBrowser.currentURI.spec.split("?")[0], properURL, "Check URL without params"); + + let actualURL = new URL(gBrowser.currentURI.spec); + let actualQueryStrings = actualURL.search.substring(1).split("&"); + actualQueryStrings.sort(); + Assert.equal(actualQueryStrings.length, queryStrings.length, "Check number of params"); + + for (let i = 0; i < queryStrings.length; i++) { + Assert.equal(actualQueryStrings[i], queryStrings[i], "Check parameter " + i); + } + + tabOpened = true; + }); + + let client = new FxAccountsOAuthClient({ + parameters: { + state: "state", + client_id: "client_id", + oauth_uri: HTTP_PATH, + content_uri: HTTP_PATH, + action: "force_auth", + email: "test@invalid.com" + }, + authorizationEndpoint: HTTP_ENDPOINT + }); + + client.onComplete = function(tokenData) { + Assert.ok(tabOpened); + Assert.equal(tokenData.code, "code1"); + Assert.equal(tokenData.state, "state"); + resolve(); + }; + + client.onError = reject; + + client.launchWebFlow(); + }); + } + }, + { + desc: "FxA OAuth - should receive an error when there's a state mismatch", + run: function () { + return new Promise(function(resolve, reject) { + let tabOpened = false; + + waitForTab(function (tab) { + Assert.ok("Tab successfully opened"); + + // It should have passed in the expected non-matching state value. + let queryString = gBrowser.currentURI.spec.split("?")[1]; + Assert.ok(queryString.indexOf('state=different-state') >= 0); + + tabOpened = true; + }); + + let client = new FxAccountsOAuthClient({ + parameters: { + state: "different-state", + client_id: "client_id", + oauth_uri: HTTP_PATH, + content_uri: HTTP_PATH, + }, + authorizationEndpoint: HTTP_ENDPOINT + }); + + client.onComplete = reject; + + client.onError = function(err) { + Assert.ok(tabOpened); + Assert.equal(err.message, "OAuth flow failed. State doesn't match"); + resolve(); + }; + + client.launchWebFlow(); + }); + } + }, + { + desc: "FxA OAuth - should be able to request keys during OAuth flow", + run: function () { + return new Promise(function(resolve, reject) { + let tabOpened = false; + + waitForTab(function (tab) { + Assert.ok("Tab successfully opened"); + + // It should have asked for keys. + let queryString = gBrowser.currentURI.spec.split('?')[1]; + Assert.ok(queryString.indexOf('keys=true') >= 0); + + tabOpened = true; + }); + + let client = new FxAccountsOAuthClient({ + parameters: { + state: "state", + client_id: "client_id", + oauth_uri: HTTP_PATH, + content_uri: HTTP_PATH, + keys: true, + }, + authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS + }); + + client.onComplete = function(tokenData, keys) { + Assert.ok(tabOpened); + Assert.equal(tokenData.code, "code1"); + Assert.equal(tokenData.state, "state"); + Assert.deepEqual(keys.kAr, {k: "kAr"}); + Assert.deepEqual(keys.kBr, {k: "kBr"}); + resolve(); + }; + + client.onError = reject; + + client.launchWebFlow(); + }); + } + }, + { + desc: "FxA OAuth - should not receive keys if not explicitly requested", + run: function () { + return new Promise(function(resolve, reject) { + let tabOpened = false; + + waitForTab(function (tab) { + Assert.ok("Tab successfully opened"); + + // It should not have asked for keys. + let queryString = gBrowser.currentURI.spec.split('?')[1]; + Assert.ok(queryString.indexOf('keys=true') == -1); + + tabOpened = true; + }); + + let client = new FxAccountsOAuthClient({ + parameters: { + state: "state", + client_id: "client_id", + oauth_uri: HTTP_PATH, + content_uri: HTTP_PATH + }, + // This endpoint will cause the completion message to contain keys. + authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS + }); + + client.onComplete = function(tokenData, keys) { + Assert.ok(tabOpened); + Assert.equal(tokenData.code, "code1"); + Assert.equal(tokenData.state, "state"); + Assert.strictEqual(keys, undefined); + resolve(); + }; + + client.onError = reject; + + client.launchWebFlow(); + }); + } + }, + { + desc: "FxA OAuth - should receive an error if keys could not be obtained", + run: function () { + return new Promise(function(resolve, reject) { + let tabOpened = false; + + waitForTab(function (tab) { + Assert.ok("Tab successfully opened"); + + // It should have asked for keys. + let queryString = gBrowser.currentURI.spec.split('?')[1]; + Assert.ok(queryString.indexOf('keys=true') >= 0); + + tabOpened = true; + }); + + let client = new FxAccountsOAuthClient({ + parameters: { + state: "state", + client_id: "client_id", + oauth_uri: HTTP_PATH, + content_uri: HTTP_PATH, + keys: true, + }, + // This endpoint will cause the completion message not to contain keys. + authorizationEndpoint: HTTP_ENDPOINT + }); + + client.onComplete = reject; + + client.onError = function(err) { + Assert.ok(tabOpened); + Assert.equal(err.message, "OAuth flow failed. Keys were not returned"); + resolve(); + }; + + client.launchWebFlow(); + }); + } + } +]; // gTests + +function waitForTab(aCallback) { + let container = gBrowser.tabContainer; + container.addEventListener("TabOpen", function tabOpener(event) { + container.removeEventListener("TabOpen", tabOpener, false); + gBrowser.addEventListener("load", function listener() { + gBrowser.removeEventListener("load", listener, true); + let tab = event.target; + aCallback(tab); + }, true); + }, false); +} + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist"; + let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref); + let newWhitelist = origWhitelist + " http://example.com"; + Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist); + try { + for (let testCase of gTests) { + info("Running: " + testCase.desc); + yield testCase.run(); + } + } finally { + Services.prefs.clearUserPref(webchannelWhitelistPref); + } + }).then(finish, ex => { + Assert.ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} diff --git a/browser/base/content/test/general/browser_fxa_oauth_with_keys.html b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html new file mode 100644 index 000000000..2c28f7088 --- /dev/null +++ b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>fxa_oauth_test</title> +</head> +<body> +<script> + window.onload = function() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + // Note: This intentionally sends a string instead of an object, to ensure both work + // (see browser_fxa_oauth.html for the other test) + detail: JSON.stringify({ + id: "oauth_client_id", + message: { + command: "oauth_complete", + data: { + state: "state", + code: "code1", + closeWindow: "signin", + // Keys normally contain more information, but this is enough + // to keep Loop's tests happy. + keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }}, + }, + }, + }), + }); + + window.dispatchEvent(event); + }; +</script> +</body> +</html> diff --git a/browser/base/content/test/general/browser_fxa_web_channel.html b/browser/base/content/test/general/browser_fxa_web_channel.html new file mode 100644 index 000000000..be5631ff1 --- /dev/null +++ b/browser/base/content/test/general/browser_fxa_web_channel.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>fxa_web_channel_test</title> +</head> +<body> +<script> + var webChannelId = "account_updates_test"; + + window.onload = function() { + var testName = window.location.search.replace(/^\?/, ""); + + switch (testName) { + case "profile_change": + test_profile_change(); + break; + case "login": + test_login(); + break; + case "can_link_account": + test_can_link_account(); + break; + case "logout": + test_logout(); + break; + case "delete": + test_delete(); + break; + } + }; + + function test_profile_change() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: webChannelId, + message: { + command: "profile:change", + data: { + uid: "abc123", + }, + }, + }), + }); + + window.dispatchEvent(event); + } + + function test_login() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: webChannelId, + message: { + command: "fxaccounts:login", + data: { + authAt: Date.now(), + email: "testuser@testuser.com", + keyFetchToken: 'key_fetch_token', + sessionToken: 'session_token', + uid: 'uid', + unwrapBKey: 'unwrap_b_key', + verified: true, + }, + messageId: 1, + }, + }), + }); + + window.dispatchEvent(event); + } + + function test_can_link_account() { + window.addEventListener("WebChannelMessageToContent", function (e) { + // echo any responses from the browser back to the tests on the + // fxaccounts_webchannel_response_echo WebChannel. The tests are + // listening for events and do the appropriate checks. + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: 'fxaccounts_webchannel_response_echo', + message: e.detail.message, + }) + }); + + window.dispatchEvent(event); + }, true); + + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: webChannelId, + message: { + command: "fxaccounts:can_link_account", + data: { + email: "testuser@testuser.com", + }, + messageId: 2, + }, + }), + }); + + window.dispatchEvent(event); + } + + function test_logout() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: webChannelId, + message: { + command: "fxaccounts:logout", + data: { + uid: 'uid' + }, + messageId: 3, + }, + }), + }); + + window.dispatchEvent(event); + } + + function test_delete() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: webChannelId, + message: { + command: "fxaccounts:delete", + data: { + uid: 'uid' + }, + messageId: 4, + }, + }), + }); + + window.dispatchEvent(event); + } +</script> +</body> +</html> diff --git a/browser/base/content/test/general/browser_fxa_web_channel.js b/browser/base/content/test/general/browser_fxa_web_channel.js new file mode 100644 index 000000000..eb0167ffb --- /dev/null +++ b/browser/base/content/test/general/browser_fxa_web_channel.js @@ -0,0 +1,210 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () { + return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {}); +}); + +XPCOMUtils.defineLazyModuleGetter(this, "WebChannel", + "resource://gre/modules/WebChannel.jsm"); + +// FxAccountsWebChannel isn't explicitly exported by FxAccountsWebChannel.jsm +// but we can get it here via a backstage pass. +var {FxAccountsWebChannel} = Components.utils.import("resource://gre/modules/FxAccountsWebChannel.jsm", {}); + +const TEST_HTTP_PATH = "http://example.com"; +const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/general/browser_fxa_web_channel.html"; +const TEST_CHANNEL_ID = "account_updates_test"; + +var gTests = [ + { + desc: "FxA Web Channel - should receive message about profile changes", + run: function* () { + let client = new FxAccountsWebChannel({ + content_uri: TEST_HTTP_PATH, + channel_id: TEST_CHANNEL_ID, + }); + let promiseObserver = new Promise((resolve, reject) => { + makeObserver(FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) { + Assert.equal(data, "abc123"); + client.tearDown(); + resolve(); + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: TEST_BASE_URL + "?profile_change" + }, function* () { + yield promiseObserver; + }); + } + }, + { + desc: "fxa web channel - login messages should notify the fxAccounts object", + run: function* () { + + let promiseLogin = new Promise((resolve, reject) => { + let login = (accountData) => { + Assert.equal(typeof accountData.authAt, 'number'); + Assert.equal(accountData.email, 'testuser@testuser.com'); + Assert.equal(accountData.keyFetchToken, 'key_fetch_token'); + Assert.equal(accountData.sessionToken, 'session_token'); + Assert.equal(accountData.uid, 'uid'); + Assert.equal(accountData.unwrapBKey, 'unwrap_b_key'); + Assert.equal(accountData.verified, true); + + client.tearDown(); + resolve(); + }; + + let client = new FxAccountsWebChannel({ + content_uri: TEST_HTTP_PATH, + channel_id: TEST_CHANNEL_ID, + helpers: { + login: login + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: TEST_BASE_URL + "?login" + }, function* () { + yield promiseLogin; + }); + } + }, + { + desc: "fxa web channel - can_link_account messages should respond", + run: function* () { + let properUrl = TEST_BASE_URL + "?can_link_account"; + + let promiseEcho = new Promise((resolve, reject) => { + + let webChannelOrigin = Services.io.newURI(properUrl, null, null); + // responses sent to content are echoed back over the + // `fxaccounts_webchannel_response_echo` channel. Ensure the + // fxaccounts:can_link_account message is responded to. + let echoWebChannel = new WebChannel('fxaccounts_webchannel_response_echo', webChannelOrigin); + echoWebChannel.listen((webChannelId, message, target) => { + Assert.equal(message.command, 'fxaccounts:can_link_account'); + Assert.equal(message.messageId, 2); + Assert.equal(message.data.ok, true); + + client.tearDown(); + echoWebChannel.stopListening(); + + resolve(); + }); + + let client = new FxAccountsWebChannel({ + content_uri: TEST_HTTP_PATH, + channel_id: TEST_CHANNEL_ID, + helpers: { + shouldAllowRelink(acctName) { + return acctName === 'testuser@testuser.com'; + } + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: properUrl + }, function* () { + yield promiseEcho; + }); + } + }, + { + desc: "fxa web channel - logout messages should notify the fxAccounts object", + run: function* () { + let promiseLogout = new Promise((resolve, reject) => { + let logout = (uid) => { + Assert.equal(uid, 'uid'); + + client.tearDown(); + resolve(); + }; + + let client = new FxAccountsWebChannel({ + content_uri: TEST_HTTP_PATH, + channel_id: TEST_CHANNEL_ID, + helpers: { + logout: logout + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: TEST_BASE_URL + "?logout" + }, function* () { + yield promiseLogout; + }); + } + }, + { + desc: "fxa web channel - delete messages should notify the fxAccounts object", + run: function* () { + let promiseDelete = new Promise((resolve, reject) => { + let logout = (uid) => { + Assert.equal(uid, 'uid'); + + client.tearDown(); + resolve(); + }; + + let client = new FxAccountsWebChannel({ + content_uri: TEST_HTTP_PATH, + channel_id: TEST_CHANNEL_ID, + helpers: { + logout: logout + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: TEST_BASE_URL + "?delete" + }, function* () { + yield promiseDelete; + }); + } + } +]; // gTests + +function makeObserver(aObserveTopic, aObserveFunc) { + let callback = function (aSubject, aTopic, aData) { + if (aTopic == aObserveTopic) { + removeMe(); + aObserveFunc(aSubject, aTopic, aData); + } + }; + + function removeMe() { + Services.obs.removeObserver(callback, aObserveTopic); + } + + Services.obs.addObserver(callback, aObserveTopic, false); + return removeMe; +} + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + for (let testCase of gTests) { + info("Running: " + testCase.desc); + yield testCase.run(); + } + }).then(finish, ex => { + Assert.ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} diff --git a/browser/base/content/test/general/browser_fxaccounts.js b/browser/base/content/test/general/browser_fxaccounts.js new file mode 100644 index 000000000..0f68286dc --- /dev/null +++ b/browser/base/content/test/general/browser_fxaccounts.js @@ -0,0 +1,261 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var {Log} = Cu.import("resource://gre/modules/Log.jsm", {}); +var {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +var {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {}); +var FxAccountsCommon = {}; +Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon); + +const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/"; + +// instrument gFxAccounts to send observer notifications when it's done +// what it does. +(function() { + let unstubs = {}; // The original functions we stub out. + + // The stub functions. + let stubs = { + updateAppMenuItem: function() { + return unstubs['updateAppMenuItem'].call(gFxAccounts).then(() => { + Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateAppMenuItem", null); + }); + }, + // Opening preferences is trickier than it should be as leaks are reported + // due to the promises it fires off at load time and there's no clear way to + // know when they are done. + // So just ensure openPreferences is called rather than whether it opens. + openPreferences: function() { + Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences", null); + } + }; + + for (let name in stubs) { + unstubs[name] = gFxAccounts[name]; + gFxAccounts[name] = stubs[name]; + } + // and undo our damage at the end. + registerCleanupFunction(() => { + for (let name in unstubs) { + gFxAccounts[name] = unstubs[name]; + } + stubs = unstubs = null; + }); +})(); + +// Other setup/cleanup +var newTab; + +Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", + TEST_ROOT + "accounts_testRemoteCommands.html"); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri"); + Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri"); + gBrowser.removeTab(newTab); +}); + +add_task(function* initialize() { + // Set a new tab with something other than about:blank, so it doesn't get reused. + // We must wait for it to load or the promiseTabOpen() call in the next test + // gets confused. + newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false}); + yield promiseTabLoaded(newTab); +}); + +// The elements we care about. +var panelUILabel = document.getElementById("PanelUI-fxa-label"); +var panelUIStatus = document.getElementById("PanelUI-fxa-status"); +var panelUIFooter = document.getElementById("PanelUI-footer-fxa"); + +// The tests +add_task(function* test_nouser() { + let user = yield fxAccounts.getSignedInUser(); + Assert.strictEqual(user, null, "start with no user signed in"); + let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem"); + Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, null); + yield promiseUpdateDone; + + // Check the world - the FxA footer area is visible as it is offering a signin. + Assert.ok(isFooterVisible()) + + Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel")); + Assert.equal(panelUIStatus.getAttribute("tooltiptext"), panelUIStatus.getAttribute("signedinTooltiptext")); + Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out"); + Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out"); + + let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences"); + panelUIStatus.click(); + yield promisePreferencesOpened; +}); + +/* +XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds. + +add_task(function* test_unverifiedUser() { + let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem"); + yield setSignedInUser(false); // this will fire the observer that does the update. + yield promiseUpdateDone; + + // Check the world. + Assert.ok(isFooterVisible()) + + Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com"); + Assert.equal(panelUIStatus.getAttribute("tooltiptext"), + panelUIStatus.getAttribute("signedinTooltiptext")); + Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin"); + let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences"); + panelUIStatus.click(); + yield promisePreferencesOpened + yield signOut(); +}); +*/ + +add_task(function* test_verifiedUserEmptyProfile() { + // We see 2 updateAppMenuItem() calls - one for the signedInUser and one after + // we first fetch the profile. We want them both to fire or we aren't testing + // the state we think we are testing. + let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2); + gFxAccounts._cachedProfile = null; + configureProfileURL({}); // successful but empty profile. + yield setSignedInUser(true); // this will fire the observer that does the update. + yield promiseUpdateDone; + + // Check the world. + Assert.ok(isFooterVisible()) + Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com"); + Assert.equal(panelUIStatus.getAttribute("tooltiptext"), + panelUIStatus.getAttribute("signedinTooltiptext")); + Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin"); + + let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences"); + panelUIStatus.click(); + yield promisePreferencesOpened; + yield signOut(); +}); + +add_task(function* test_verifiedUserDisplayName() { + let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2); + gFxAccounts._cachedProfile = null; + configureProfileURL({ displayName: "Test User Display Name" }); + yield setSignedInUser(true); // this will fire the observer that does the update. + yield promiseUpdateDone; + + Assert.ok(isFooterVisible()) + Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name"); + Assert.equal(panelUIStatus.getAttribute("tooltiptext"), + panelUIStatus.getAttribute("signedinTooltiptext")); + Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin"); + yield signOut(); +}); + +add_task(function* test_verifiedUserProfileFailure() { + // profile failure means only one observer fires. + let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 1); + gFxAccounts._cachedProfile = null; + configureProfileURL(null, 500); + yield setSignedInUser(true); // this will fire the observer that does the update. + yield promiseUpdateDone; + + Assert.ok(isFooterVisible()) + Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com"); + Assert.equal(panelUIStatus.getAttribute("tooltiptext"), + panelUIStatus.getAttribute("signedinTooltiptext")); + Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin"); + yield signOut(); +}); + +// Helpers. +function isFooterVisible() { + let style = window.getComputedStyle(panelUIFooter); + return style.getPropertyValue("display") == "flex"; +} + +function configureProfileURL(profile, responseStatus = 200) { + let responseBody = profile ? JSON.stringify(profile) : ""; + let url = TEST_ROOT + "fxa_profile_handler.sjs?" + + "responseStatus=" + responseStatus + + "responseBody=" + responseBody + + // This is a bit cheeky - the FxA code will just append "/profile" + // to the preference value. We arrange for this to be seen by our + // .sjs as part of the query string. + "&path="; + + Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url); +} + +function promiseObserver(topic, count = 1) { + return new Promise(resolve => { + let obs = (aSubject, aTopic, aData) => { + if (--count == 0) { + Services.obs.removeObserver(obs, aTopic); + resolve(aSubject); + } + } + Services.obs.addObserver(obs, topic, false); + }); +} + +// Stolen from browser_aboutHome.js +function promiseWaitForEvent(node, type, capturing) { + return new Promise((resolve) => { + node.addEventListener(type, function listener(event) { + node.removeEventListener(type, listener, capturing); + resolve(event); + }, capturing); + }); +} + +var promiseTabOpen = Task.async(function*(urlBase) { + info("Waiting for tab to open..."); + let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true); + let tab = event.target; + yield promiseTabLoadEvent(tab); + ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase), + "Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase); + let whenUnloaded = promiseTabUnloaded(tab); + gBrowser.removeTab(tab); + yield whenUnloaded; +}); + +function promiseTabUnloaded(tab) +{ + return new Promise(resolve => { + info("Wait for tab to unload"); + function handle(event) { + tab.linkedBrowser.removeEventListener("unload", handle, true); + info("Got unload event"); + resolve(event); + } + tab.linkedBrowser.addEventListener("unload", handle, true, true); + }); +} + +// FxAccounts helpers. +function setSignedInUser(verified) { + let data = { + email: "foo@example.com", + uid: "1234@lcip.org", + assertion: "foobar", + sessionToken: "dead", + kA: "beef", + kB: "cafe", + verified: verified, + + oauthTokens: { + // a token for the profile server. + profile: "key value", + } + } + return fxAccounts.setSignedInUser(data); +} + +var signOut = Task.async(function* () { + // This test needs to make sure that any updates for the logout have + // completed before starting the next test, or we see the observer + // notifications get out of sync. + let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem"); + // we always want a "localOnly" signout here... + yield fxAccounts.signOut(true); + yield promiseUpdateDone; +}); diff --git a/browser/base/content/test/general/browser_gZipOfflineChild.js b/browser/base/content/test/general/browser_gZipOfflineChild.js new file mode 100644 index 000000000..09691bed8 --- /dev/null +++ b/browser/base/content/test/general/browser_gZipOfflineChild.js @@ -0,0 +1,80 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/test_offline_gzip.html" + +registerCleanupFunction(function() { + // Clean up after ourself + let uri = Services.io.newURI(URL, null, null); + let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {}); + Services.perms.removeFromPrincipal(principal, "offline-app"); + Services.prefs.clearUserPref("offline-apps.allow_by_default"); +}); + +var cacheCount = 0; +var intervalID = 0; + +// +// Handle "message" events which are posted from the iframe upon +// offline cache events. +// +function handleMessageEvents(event) { + cacheCount++; + switch (cacheCount) { + case 1: + // This is the initial caching off offline data. + is(event.data, "oncache", "Child was successfully cached."); + // Reload the frame; this will generate an error message + // in the case of bug 501422. + event.source.location.reload(); + // Use setInterval to repeatedly call a function which + // checks that one of two things has occurred: either + // the offline cache is udpated (which means our iframe + // successfully reloaded), or the string "error" appears + // in the iframe, as in the case of bug 501422. + intervalID = setInterval(function() { + // Sometimes document.body may not exist, and trying to access + // it will throw an exception, so handle this case. + try { + var bodyInnerHTML = event.source.document.body.innerHTML; + } + catch (e) { + bodyInnerHTML = ""; + } + if (cacheCount == 2 || bodyInnerHTML.includes("error")) { + clearInterval(intervalID); + is(cacheCount, 2, "frame not reloaded successfully"); + if (cacheCount != 2) { + finish(); + } + } + }, 100); + break; + case 2: + is(event.data, "onupdate", "Child was successfully updated."); + clearInterval(intervalID); + finish(); + break; + default: + // how'd we get here? + ok(false, "cacheCount not 1 or 2"); + } +} + +function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref("offline-apps.allow_by_default", true); + + // Open a new tab. + gBrowser.selectedTab = gBrowser.addTab(URL); + registerCleanupFunction(() => gBrowser.removeCurrentTab()); + + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + let window = gBrowser.selectedBrowser.contentWindow; + + window.addEventListener("message", handleMessageEvents, false); + }); +} diff --git a/browser/base/content/test/general/browser_gestureSupport.js b/browser/base/content/test/general/browser_gestureSupport.js new file mode 100644 index 000000000..b31cad31d --- /dev/null +++ b/browser/base/content/test/general/browser_gestureSupport.js @@ -0,0 +1,670 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Simple gestures tests +// +// These tests require the ability to disable the fact that the +// Firefox chrome intentionally prevents "simple gesture" events from +// reaching web content. + +var test_utils; +var test_commandset; +var test_prefBranch = "browser.gesture."; + +function test() +{ + waitForExplicitFinish(); + + // Disable the default gestures support during the test + gGestureSupport.init(false); + + test_utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + + // Run the tests of "simple gesture" events generally + test_EnsureConstantsAreDisjoint(); + test_TestEventListeners(); + test_TestEventCreation(); + + // Reenable the default gestures support. The remaining tests target + // the Firefox gesture functionality. + gGestureSupport.init(true); + + // Test Firefox's gestures support. + test_commandset = document.getElementById("mainCommandSet"); + test_swipeGestures(); + test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture"); + test_thresholdGesture("pinch", "out", "in", "MozMagnifyGesture"); + test_rotateGestures(); +} + +var test_eventCount = 0; +var test_expectedType; +var test_expectedDirection; +var test_expectedDelta; +var test_expectedModifiers; +var test_expectedClickCount; +var test_imageTab; + +function test_gestureListener(evt) +{ + is(evt.type, test_expectedType, + "evt.type (" + evt.type + ") does not match expected value"); + is(evt.target, test_utils.elementFromPoint(20, 20, false, false), + "evt.target (" + evt.target + ") does not match expected value"); + is(evt.clientX, 20, + "evt.clientX (" + evt.clientX + ") does not match expected value"); + is(evt.clientY, 20, + "evt.clientY (" + evt.clientY + ") does not match expected value"); + isnot(evt.screenX, 0, + "evt.screenX (" + evt.screenX + ") does not match expected value"); + isnot(evt.screenY, 0, + "evt.screenY (" + evt.screenY + ") does not match expected value"); + + is(evt.direction, test_expectedDirection, + "evt.direction (" + evt.direction + ") does not match expected value"); + is(evt.delta, test_expectedDelta, + "evt.delta (" + evt.delta + ") does not match expected value"); + + is(evt.shiftKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.SHIFT_MASK) != 0, + "evt.shiftKey did not match expected value"); + is(evt.ctrlKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.CONTROL_MASK) != 0, + "evt.ctrlKey did not match expected value"); + is(evt.altKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.ALT_MASK) != 0, + "evt.altKey did not match expected value"); + is(evt.metaKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.META_MASK) != 0, + "evt.metaKey did not match expected value"); + + if (evt.type == "MozTapGesture") { + is(evt.clickCount, test_expectedClickCount, "evt.clickCount does not match"); + } + + test_eventCount++; +} + +function test_helper1(type, direction, delta, modifiers) +{ + // Setup the expected values + test_expectedType = type; + test_expectedDirection = direction; + test_expectedDelta = delta; + test_expectedModifiers = modifiers; + + let expectedEventCount = test_eventCount + 1; + + document.addEventListener(type, test_gestureListener, true); + test_utils.sendSimpleGestureEvent(type, 20, 20, direction, delta, modifiers); + document.removeEventListener(type, test_gestureListener, true); + + is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener"); +} + +function test_clicks(type, clicks) +{ + // Setup the expected values + test_expectedType = type; + test_expectedDirection = 0; + test_expectedDelta = 0; + test_expectedModifiers = 0; + test_expectedClickCount = clicks; + + let expectedEventCount = test_eventCount + 1; + + document.addEventListener(type, test_gestureListener, true); + test_utils.sendSimpleGestureEvent(type, 20, 20, 0, 0, 0, clicks); + document.removeEventListener(type, test_gestureListener, true); + + is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener"); +} + +function test_TestEventListeners() +{ + let e = test_helper1; // easier to type this name + + // Swipe gesture animation events + e("MozSwipeGestureStart", 0, -0.7, 0); + e("MozSwipeGestureUpdate", 0, -0.4, 0); + e("MozSwipeGestureEnd", 0, 0, 0); + e("MozSwipeGestureStart", 0, 0.6, 0); + e("MozSwipeGestureUpdate", 0, 0.3, 0); + e("MozSwipeGestureEnd", 0, 1, 0); + + // Swipe gesture event + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0); + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0); + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_UP, 0.0, 0); + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_DOWN, 0.0, 0); + e("MozSwipeGesture", + SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0); + e("MozSwipeGesture", + SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0); + e("MozSwipeGesture", + SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0); + e("MozSwipeGesture", + SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0); + + // magnify gesture events + e("MozMagnifyGestureStart", 0, 50.0, 0); + e("MozMagnifyGestureUpdate", 0, -25.0, 0); + e("MozMagnifyGestureUpdate", 0, 5.0, 0); + e("MozMagnifyGesture", 0, 30.0, 0); + + // rotate gesture events + e("MozRotateGestureStart", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0); + e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE, -13.0, 0); + e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_CLOCKWISE, 13.0, 0); + e("MozRotateGesture", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0); + + // Tap and presstap gesture events + test_clicks("MozTapGesture", 1); + test_clicks("MozTapGesture", 2); + test_clicks("MozTapGesture", 3); + test_clicks("MozPressTapGesture", 1); + + // simple delivery test for edgeui gestures + e("MozEdgeUIStarted", 0, 0, 0); + e("MozEdgeUICanceled", 0, 0, 0); + e("MozEdgeUICompleted", 0, 0, 0); + + // event.shiftKey + let modifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); + + // event.metaKey + modifier = Components.interfaces.nsIDOMEvent.META_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); + + // event.altKey + modifier = Components.interfaces.nsIDOMEvent.ALT_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); + + // event.ctrlKey + modifier = Components.interfaces.nsIDOMEvent.CONTROL_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); +} + +function test_eventDispatchListener(evt) +{ + test_eventCount++; + evt.stopPropagation(); +} + +function test_helper2(type, direction, delta, altKey, ctrlKey, shiftKey, metaKey) +{ + let event = null; + let successful; + + try { + event = document.createEvent("SimpleGestureEvent"); + successful = true; + } + catch (ex) { + successful = false; + } + ok(successful, "Unable to create SimpleGestureEvent"); + + try { + event.initSimpleGestureEvent(type, true, true, window, 1, + 10, 10, 10, 10, + ctrlKey, altKey, shiftKey, metaKey, + 1, window, + 0, direction, delta, 0); + successful = true; + } + catch (ex) { + successful = false; + } + ok(successful, "event.initSimpleGestureEvent should not fail"); + + // Make sure the event fields match the expected values + is(event.type, type, "Mismatch on evt.type"); + is(event.direction, direction, "Mismatch on evt.direction"); + is(event.delta, delta, "Mismatch on evt.delta"); + is(event.altKey, altKey, "Mismatch on evt.altKey"); + is(event.ctrlKey, ctrlKey, "Mismatch on evt.ctrlKey"); + is(event.shiftKey, shiftKey, "Mismatch on evt.shiftKey"); + is(event.metaKey, metaKey, "Mismatch on evt.metaKey"); + is(event.view, window, "Mismatch on evt.view"); + is(event.detail, 1, "Mismatch on evt.detail"); + is(event.clientX, 10, "Mismatch on evt.clientX"); + is(event.clientY, 10, "Mismatch on evt.clientY"); + is(event.screenX, 10, "Mismatch on evt.screenX"); + is(event.screenY, 10, "Mismatch on evt.screenY"); + is(event.button, 1, "Mismatch on evt.button"); + is(event.relatedTarget, window, "Mismatch on evt.relatedTarget"); + + // Test event dispatch + let expectedEventCount = test_eventCount + 1; + document.addEventListener(type, test_eventDispatchListener, true); + document.dispatchEvent(event); + document.removeEventListener(type, test_eventDispatchListener, true); + is(expectedEventCount, test_eventCount, "Dispatched event was never received by listener"); +} + +function test_TestEventCreation() +{ + // Event creation + test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_RIGHT, 20.0, + true, false, true, false); + test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_LEFT, -20.0, + false, true, false, true); +} + +function test_EnsureConstantsAreDisjoint() +{ + let up = SimpleGestureEvent.DIRECTION_UP; + let down = SimpleGestureEvent.DIRECTION_DOWN; + let left = SimpleGestureEvent.DIRECTION_LEFT; + let right = SimpleGestureEvent.DIRECTION_RIGHT; + + let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE; + let cclockwise = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE; + + ok(up ^ down, "DIRECTION_UP and DIRECTION_DOWN are not bitwise disjoint"); + ok(up ^ left, "DIRECTION_UP and DIRECTION_LEFT are not bitwise disjoint"); + ok(up ^ right, "DIRECTION_UP and DIRECTION_RIGHT are not bitwise disjoint"); + ok(down ^ left, "DIRECTION_DOWN and DIRECTION_LEFT are not bitwise disjoint"); + ok(down ^ right, "DIRECTION_DOWN and DIRECTION_RIGHT are not bitwise disjoint"); + ok(left ^ right, "DIRECTION_LEFT and DIRECTION_RIGHT are not bitwise disjoint"); + ok(clockwise ^ cclockwise, "ROTATION_CLOCKWISE and ROTATION_COUNTERCLOCKWISE are not bitwise disjoint"); +} + +// Helper for test of latched event processing. Emits the actual +// gesture events to test whether the commands associated with the +// gesture will only trigger once for each direction of movement. +function test_emitLatchedEvents(eventPrefix, initialDelta, cmd) +{ + let cumulativeDelta = 0; + let isIncreasing = initialDelta > 0; + + let expect = {}; + // Reset the call counters and initialize expected values + for (let dir in cmd) + cmd[dir].callCount = expect[dir] = 0; + + let check = (aDir, aMsg) => ok(cmd[aDir].callCount == expect[aDir], aMsg); + let checkBoth = function(aNum, aInc, aDec) { + let prefix = "Step " + aNum + ": "; + check("inc", prefix + aInc); + check("dec", prefix + aDec); + }; + + // Send the "Start" event. + test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, initialDelta, 0); + cumulativeDelta += initialDelta; + if (isIncreasing) { + expect.inc++; + checkBoth(1, "Increasing command was not triggered", "Decreasing command was triggered"); + } else { + expect.dec++; + checkBoth(1, "Increasing command was triggered", "Decreasing command was not triggered"); + } + + // Send random values in the same direction and ensure neither + // command triggers. + for (let i = 0; i < 5; i++) { + let delta = Math.random() * (isIncreasing ? 100 : -100); + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0); + cumulativeDelta += delta; + checkBoth(2, "Increasing command was triggered", "Decreasing command was triggered"); + } + + // Now go back in the opposite direction. + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, + - initialDelta, 0); + cumulativeDelta += - initialDelta; + if (isIncreasing) { + expect.dec++; + checkBoth(3, "Increasing command was triggered", "Decreasing command was not triggered"); + } else { + expect.inc++; + checkBoth(3, "Increasing command was not triggered", "Decreasing command was triggered"); + } + + // Send random values in the opposite direction and ensure neither + // command triggers. + for (let i = 0; i < 5; i++) { + let delta = Math.random() * (isIncreasing ? -100 : 100); + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0); + cumulativeDelta += delta; + checkBoth(4, "Increasing command was triggered", "Decreasing command was triggered"); + } + + // Go back to the original direction. The original command should trigger. + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, + initialDelta, 0); + cumulativeDelta += initialDelta; + if (isIncreasing) { + expect.inc++; + checkBoth(5, "Increasing command was not triggered", "Decreasing command was triggered"); + } else { + expect.dec++; + checkBoth(5, "Increasing command was triggered", "Decreasing command was not triggered"); + } + + // Send the wrap-up event. No commands should be triggered. + test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, cumulativeDelta, 0); + checkBoth(6, "Increasing command was triggered", "Decreasing command was triggered"); +} + +function test_addCommand(prefName, id) +{ + let cmd = test_commandset.appendChild(document.createElement("command")); + cmd.setAttribute("id", id); + cmd.setAttribute("oncommand", "this.callCount++;"); + + cmd.origPrefName = prefName; + cmd.origPrefValue = gPrefService.getCharPref(prefName); + gPrefService.setCharPref(prefName, id); + + return cmd; +} + +function test_removeCommand(cmd) +{ + gPrefService.setCharPref(cmd.origPrefName, cmd.origPrefValue); + test_commandset.removeChild(cmd); +} + +// Test whether latched events are only called once per direction of motion. +function test_latchedGesture(gesture, inc, dec, eventPrefix) +{ + let branch = test_prefBranch + gesture + "."; + + // Put the gesture into latched mode. + let oldLatchedValue = gPrefService.getBoolPref(branch + "latched"); + gPrefService.setBoolPref(branch + "latched", true); + + // Install the test commands for increasing and decreasing motion. + let cmd = { + inc: test_addCommand(branch + inc, "test:incMotion"), + dec: test_addCommand(branch + dec, "test:decMotion"), + }; + + // Test the gestures in each direction. + test_emitLatchedEvents(eventPrefix, 500, cmd); + test_emitLatchedEvents(eventPrefix, -500, cmd); + + // Restore the gesture to its original configuration. + gPrefService.setBoolPref(branch + "latched", oldLatchedValue); + for (let dir in cmd) + test_removeCommand(cmd[dir]); +} + +// Test whether non-latched events are triggered upon sufficient motion. +function test_thresholdGesture(gesture, inc, dec, eventPrefix) +{ + let branch = test_prefBranch + gesture + "."; + + // Disable latched mode for this gesture. + let oldLatchedValue = gPrefService.getBoolPref(branch + "latched"); + gPrefService.setBoolPref(branch + "latched", false); + + // Set the triggering threshold value to 50. + let oldThresholdValue = gPrefService.getIntPref(branch + "threshold"); + gPrefService.setIntPref(branch + "threshold", 50); + + // Install the test commands for increasing and decreasing motion. + let cmdInc = test_addCommand(branch + inc, "test:incMotion"); + let cmdDec = test_addCommand(branch + dec, "test:decMotion"); + + // Send the start event but stop short of triggering threshold. + cmdInc.callCount = cmdDec.callCount = 0; + test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, 49.5, 0); + ok(cmdInc.callCount == 0, "Increasing command was triggered"); + ok(cmdDec.callCount == 0, "Decreasing command was triggered"); + + // Now trigger the threshold. + cmdInc.callCount = cmdDec.callCount = 0; + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, 1, 0); + ok(cmdInc.callCount == 1, "Increasing command was not triggered"); + ok(cmdDec.callCount == 0, "Decreasing command was triggered"); + + // The tracking counter should go to zero. Go back the other way and + // stop short of triggering the threshold. + cmdInc.callCount = cmdDec.callCount = 0; + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -49.5, 0); + ok(cmdInc.callCount == 0, "Increasing command was triggered"); + ok(cmdDec.callCount == 0, "Decreasing command was triggered"); + + // Now cross the threshold and trigger the decreasing command. + cmdInc.callCount = cmdDec.callCount = 0; + test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -1.5, 0); + ok(cmdInc.callCount == 0, "Increasing command was triggered"); + ok(cmdDec.callCount == 1, "Decreasing command was not triggered"); + + // Send the wrap-up event. No commands should trigger. + cmdInc.callCount = cmdDec.callCount = 0; + test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, -0.5, 0); + ok(cmdInc.callCount == 0, "Increasing command was triggered"); + ok(cmdDec.callCount == 0, "Decreasing command was triggered"); + + // Restore the gesture to its original configuration. + gPrefService.setBoolPref(branch + "latched", oldLatchedValue); + gPrefService.setIntPref(branch + "threshold", oldThresholdValue); + test_removeCommand(cmdInc); + test_removeCommand(cmdDec); +} + +function test_swipeGestures() +{ + // easier to type names for the direction constants + let up = SimpleGestureEvent.DIRECTION_UP; + let down = SimpleGestureEvent.DIRECTION_DOWN; + let left = SimpleGestureEvent.DIRECTION_LEFT; + let right = SimpleGestureEvent.DIRECTION_RIGHT; + + let branch = test_prefBranch + "swipe."; + + // Install the test commands for the swipe gestures. + let cmdUp = test_addCommand(branch + "up", "test:swipeUp"); + let cmdDown = test_addCommand(branch + "down", "test:swipeDown"); + let cmdLeft = test_addCommand(branch + "left", "test:swipeLeft"); + let cmdRight = test_addCommand(branch + "right", "test:swipeRight"); + + function resetCounts() { + cmdUp.callCount = 0; + cmdDown.callCount = 0; + cmdLeft.callCount = 0; + cmdRight.callCount = 0; + } + + // UP + resetCounts(); + test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, up, 0, 0); + ok(cmdUp.callCount == 1, "Step 1: Up command was not triggered"); + ok(cmdDown.callCount == 0, "Step 1: Down command was triggered"); + ok(cmdLeft.callCount == 0, "Step 1: Left command was triggered"); + ok(cmdRight.callCount == 0, "Step 1: Right command was triggered"); + + // DOWN + resetCounts(); + test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, down, 0, 0); + ok(cmdUp.callCount == 0, "Step 2: Up command was triggered"); + ok(cmdDown.callCount == 1, "Step 2: Down command was not triggered"); + ok(cmdLeft.callCount == 0, "Step 2: Left command was triggered"); + ok(cmdRight.callCount == 0, "Step 2: Right command was triggered"); + + // LEFT + resetCounts(); + test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, left, 0, 0); + ok(cmdUp.callCount == 0, "Step 3: Up command was triggered"); + ok(cmdDown.callCount == 0, "Step 3: Down command was triggered"); + ok(cmdLeft.callCount == 1, "Step 3: Left command was not triggered"); + ok(cmdRight.callCount == 0, "Step 3: Right command was triggered"); + + // RIGHT + resetCounts(); + test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, right, 0, 0); + ok(cmdUp.callCount == 0, "Step 4: Up command was triggered"); + ok(cmdDown.callCount == 0, "Step 4: Down command was triggered"); + ok(cmdLeft.callCount == 0, "Step 4: Left command was triggered"); + ok(cmdRight.callCount == 1, "Step 4: Right command was not triggered"); + + // Make sure combinations do not trigger events. + let combos = [ up | left, up | right, down | left, down | right]; + for (let i = 0; i < combos.length; i++) { + resetCounts(); + test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, combos[i], 0, 0); + ok(cmdUp.callCount == 0, "Step 5-"+i+": Up command was triggered"); + ok(cmdDown.callCount == 0, "Step 5-"+i+": Down command was triggered"); + ok(cmdLeft.callCount == 0, "Step 5-"+i+": Left command was triggered"); + ok(cmdRight.callCount == 0, "Step 5-"+i+": Right command was triggered"); + } + + // Remove the test commands. + test_removeCommand(cmdUp); + test_removeCommand(cmdDown); + test_removeCommand(cmdLeft); + test_removeCommand(cmdRight); +} + + +function test_rotateHelperGetImageRotation(aImageElement) +{ + // Get the true image rotation from the transform matrix, bounded + // to 0 <= result < 360 + let transformValue = content.window.getComputedStyle(aImageElement, null) + .transform; + if (transformValue == "none") + return 0; + + transformValue = transformValue.split("(")[1] + .split(")")[0] + .split(","); + var rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) * + (180 / Math.PI)); + return (rotation < 0 ? rotation + 360 : rotation); +} + +function test_rotateHelperOneGesture(aImageElement, aCurrentRotation, + aDirection, aAmount, aStop) +{ + if (aAmount <= 0 || aAmount > 90) // Bound to 0 < aAmount <= 90 + return; + + // easier to type names for the direction constants + let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE; + + let delta = aAmount * (aDirection == clockwise ? 1 : -1); + + // Kill transition time on image so test isn't wrong and doesn't take 10 seconds + aImageElement.style.transitionDuration = "0s"; + + // Start the gesture, perform an update, and force flush + test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, aDirection, .001, 0); + test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, delta, 0); + aImageElement.clientTop; + + // If stop, check intermediate + if (aStop) { + // Send near-zero-delta to stop, and force flush + test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, .001, 0); + aImageElement.clientTop; + + let stopExpectedRotation = (aCurrentRotation + delta) % 360; + if (stopExpectedRotation < 0) + stopExpectedRotation += 360; + + is(stopExpectedRotation, test_rotateHelperGetImageRotation(aImageElement), + "Image rotation at gesture stop/hold: expected=" + stopExpectedRotation + + ", observed=" + test_rotateHelperGetImageRotation(aImageElement) + + ", init=" + aCurrentRotation + + ", amt=" + aAmount + + ", dir=" + (aDirection == clockwise ? "cl" : "ccl")); + } + // End it and force flush + test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, aDirection, 0, 0); + aImageElement.clientTop; + + let finalExpectedRotation; + + if (aAmount < 45 && aStop) { + // Rotate a bit, then stop. Expect no change at end of gesture. + finalExpectedRotation = aCurrentRotation; + } + else { + // Either not stopping (expect 90 degree change in aDirection), OR + // stopping but after 45, (expect 90 degree change in aDirection) + finalExpectedRotation = (aCurrentRotation + + (aDirection == clockwise ? 1 : -1) * 90) % 360; + if (finalExpectedRotation < 0) + finalExpectedRotation += 360; + } + + is(finalExpectedRotation, test_rotateHelperGetImageRotation(aImageElement), + "Image rotation gesture end: expected=" + finalExpectedRotation + + ", observed=" + test_rotateHelperGetImageRotation(aImageElement) + + ", init=" + aCurrentRotation + + ", amt=" + aAmount + + ", dir=" + (aDirection == clockwise ? "cl" : "ccl")); +} + +function test_rotateGesturesOnTab() +{ + gBrowser.selectedBrowser.removeEventListener("load", test_rotateGesturesOnTab, true); + + if (!(content.document instanceof ImageDocument)) { + ok(false, "Image document failed to open for rotation testing"); + gBrowser.removeTab(test_imageTab); + finish(); + return; + } + + // easier to type names for the direction constants + let cl = SimpleGestureEvent.ROTATION_CLOCKWISE; + let ccl = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE; + + let imgElem = content.document.body && + content.document.body.firstElementChild; + + if (!imgElem) { + ok(false, "Could not get image element on ImageDocument for rotation!"); + gBrowser.removeTab(test_imageTab); + finish(); + return; + } + + // Quick function to normalize rotation to 0 <= r < 360 + var normRot = function(rotation) { + rotation = rotation % 360; + if (rotation < 0) + rotation += 360; + return rotation; + } + + for (var initRot = 0; initRot < 360; initRot += 90) { + // Test each case: at each 90 degree snap; cl/ccl; + // amount more or less than 45; stop and hold or don't (32 total tests) + // The amount added to the initRot is where it is expected to be + test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, true ); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, false); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), cl, 55, true ); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), cl, 55, false); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, true ); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, false); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), ccl, 55, true ); + test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), ccl, 55, false); + + // Manually rotate it 90 degrees clockwise to prepare for next iteration, + // and force flush + test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, cl, .001, 0); + test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, 90, 0); + test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, .001, 0); + test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, cl, 0, 0); + imgElem.clientTop; + } + + gBrowser.removeTab(test_imageTab); + test_imageTab = null; + finish(); +} + +function test_rotateGestures() +{ + test_imageTab = gBrowser.addTab("chrome://branding/content/about-logo.png"); + gBrowser.selectedTab = test_imageTab; + + gBrowser.selectedBrowser.addEventListener("load", test_rotateGesturesOnTab, true); +} diff --git a/browser/base/content/test/general/browser_getshortcutoruri.js b/browser/base/content/test/general/browser_getshortcutoruri.js new file mode 100644 index 000000000..9ebf8e9ca --- /dev/null +++ b/browser/base/content/test/general/browser_getshortcutoruri.js @@ -0,0 +1,143 @@ +function getPostDataString(aIS) { + if (!aIS) + return null; + + var sis = Cc["@mozilla.org/scriptableinputstream;1"]. + createInstance(Ci.nsIScriptableInputStream); + sis.init(aIS); + var dataLines = sis.read(aIS.available()).split("\n"); + + // only want the last line + return dataLines[dataLines.length-1]; +} + +function keywordResult(aURL, aPostData, aIsUnsafe) { + this.url = aURL; + this.postData = aPostData; + this.isUnsafe = aIsUnsafe; +} + +function keyWordData() {} +keyWordData.prototype = { + init: function(aKeyWord, aURL, aPostData, aSearchWord) { + this.keyword = aKeyWord; + this.uri = makeURI(aURL); + this.postData = aPostData; + this.searchWord = aSearchWord; + + this.method = (this.postData ? "POST" : "GET"); + } +} + +function bmKeywordData(aKeyWord, aURL, aPostData, aSearchWord) { + this.init(aKeyWord, aURL, aPostData, aSearchWord); +} +bmKeywordData.prototype = new keyWordData(); + +function searchKeywordData(aKeyWord, aURL, aPostData, aSearchWord) { + this.init(aKeyWord, aURL, aPostData, aSearchWord); +} +searchKeywordData.prototype = new keyWordData(); + +var testData = [ + [new bmKeywordData("bmget", "http://bmget/search=%s", null, "foo"), + new keywordResult("http://bmget/search=foo", null)], + + [new bmKeywordData("bmpost", "http://bmpost/", "search=%s", "foo2"), + new keywordResult("http://bmpost/", "search=foo2")], + + [new bmKeywordData("bmpostget", "http://bmpostget/search1=%s", "search2=%s", "foo3"), + new keywordResult("http://bmpostget/search1=foo3", "search2=foo3")], + + [new bmKeywordData("bmget-nosearch", "http://bmget-nosearch/", null, ""), + new keywordResult("http://bmget-nosearch/", null)], + + [new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"), + new keywordResult("http://searchget/?search=foo4", null, true)], + + [new searchKeywordData("searchpost", "http://searchpost/", "search={searchTerms}", "foo5"), + new keywordResult("http://searchpost/", "search=foo5", true)], + + [new searchKeywordData("searchpostget", "http://searchpostget/?search1={searchTerms}", "search2={searchTerms}", "foo6"), + new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6", true)], + + // Bookmark keywords that don't take parameters should not be activated if a + // parameter is passed (bug 420328). + [new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"), + new keywordResult(null, null, true)], + [new bmKeywordData("bmpost-noparam", "http://bmpost-noparam/", "not_a=param", "foo8"), + new keywordResult(null, null, true)], + + // Test escaping (%s = escaped, %S = raw) + // UTF-8 default + [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "foé"), + new keywordResult("http://bmget/?esc=fo%C3%A9&raw=foé", null)], + // Explicitly-defined ISO-8859-1 + [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "foé"), + new keywordResult("http://bmget/?esc=fo%E9&raw=foé", null)], + + // Bug 359809: Test escaping +, /, and @ + // UTF-8 default + [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "+/@"), + new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)], + // Explicitly-defined ISO-8859-1 + [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"), + new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)], + + // Test using a non-bmKeywordData object, to test the behavior of + // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for + // bmKeywordData objects) + [{keyword: "http://gavinsharp.com"}, + new keywordResult(null, null, true)] +]; + +add_task(function* test_getshortcutoruri() { + yield setupKeywords(); + + for (let item of testData) { + let [data, result] = item; + + let query = data.keyword; + if (data.searchWord) + query += " " + data.searchWord; + let returnedData = yield getShortcutOrURIAndPostData(query); + // null result.url means we should expect the same query we sent in + let expected = result.url || query; + is(returnedData.url, expected, "got correct URL for " + data.keyword); + is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword); + is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword); + } + + yield cleanupKeywords(); +}); + +var folder = null; +var gAddedEngines = []; + +function* setupKeywords() { + folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "keyword-test" }); + for (let item of testData) { + let data = item[0]; + if (data instanceof bmKeywordData) { + yield PlacesUtils.bookmarks.insert({ url: data.uri, parentGuid: folder.guid }); + yield PlacesUtils.keywords.insert({ keyword: data.keyword, url: data.uri.spec, postData: data.postData }); + } + + if (data instanceof searchKeywordData) { + Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec); + let addedEngine = Services.search.getEngineByName(data.keyword); + if (data.postData) { + let [paramName, paramValue] = data.postData.split("="); + addedEngine.addParam(paramName, paramValue, null); + } + gAddedEngines.push(addedEngine); + } + } +} + +function* cleanupKeywords() { + PlacesUtils.bookmarks.remove(folder); + gAddedEngines.map(Services.search.removeEngine); +} diff --git a/browser/base/content/test/general/browser_hide_removing.js b/browser/base/content/test/general/browser_hide_removing.js new file mode 100644 index 000000000..be62e2d89 --- /dev/null +++ b/browser/base/content/test/general/browser_hide_removing.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Bug 587922: tabs don't get removed if they're hidden + +function test() { + waitForExplicitFinish(); + + // Add a tab that will get removed and hidden + let testTab = gBrowser.addTab("about:blank", {skipAnimation: true}); + is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs"); + gBrowser.selectedTab = testTab; + + let numVisBeforeHide, numVisAfterHide; + gBrowser.tabContainer.addEventListener("TabSelect", function() { + gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false); + + // While the next tab is being selected, hide the removing tab + numVisBeforeHide = gBrowser.visibleTabs.length; + gBrowser.hideTab(testTab); + numVisAfterHide = gBrowser.visibleTabs.length; + }, false); + gBrowser.removeTab(testTab, {animate: true}); + + // Make sure the tab gets removed at the end of the animation by polling + (function checkRemoved() { + return setTimeout(function() { + if (gBrowser.tabs.length != 1) { + checkRemoved(); + return; + } + + is(numVisBeforeHide, 1, "animated remove has in 1 tab left"); + is(numVisAfterHide, 1, "hiding a removing tab is also has 1 tab"); + finish(); + }, 50); + })(); +} diff --git a/browser/base/content/test/general/browser_homeDrop.js b/browser/base/content/test/general/browser_homeDrop.js new file mode 100644 index 000000000..6e87963d5 --- /dev/null +++ b/browser/base/content/test/general/browser_homeDrop.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function*() { + let HOMEPAGE_PREF = "browser.startup.homepage"; + + let homepageStr = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + homepageStr.data = "about:mozilla"; + yield pushPrefs([HOMEPAGE_PREF, homepageStr, Ci.nsISupportsString]); + + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + // Since synthesizeDrop triggers the srcElement, need to use another button. + let dragSrcElement = document.getElementById("downloads-button"); + ok(dragSrcElement, "Downloads button exists"); + let homeButton = document.getElementById("home-button"); + ok(homeButton, "home button present"); + + function* drop(dragData, homepage) { + let setHomepageDialogPromise = BrowserTestUtils.domWindowOpened(); + + EventUtils.synthesizeDrop(dragSrcElement, homeButton, dragData, "copy", window); + + let setHomepageDialog = yield setHomepageDialogPromise; + ok(true, "dialog appeared in response to home button drop"); + yield BrowserTestUtils.waitForEvent(setHomepageDialog, "load", false); + + let setHomepagePromise = new Promise(function(resolve) { + let observer = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), + observe: function(subject, topic, data) { + is(topic, "nsPref:changed", "observed correct topic"); + is(data, HOMEPAGE_PREF, "observed correct data"); + let modified = Services.prefs.getComplexValue(HOMEPAGE_PREF, + Ci.nsISupportsString); + is(modified.data, homepage, "homepage is set correctly"); + Services.prefs.removeObserver(HOMEPAGE_PREF, observer); + + Services.prefs.setComplexValue(HOMEPAGE_PREF, + Ci.nsISupportsString, homepageStr); + + resolve(); + } + }; + Services.prefs.addObserver(HOMEPAGE_PREF, observer, false); + }); + + setHomepageDialog.document.documentElement.acceptDialog(); + + yield setHomepagePromise; + } + + function dropInvalidURI() { + return new Promise(resolve => { + let consoleListener = { + observe: function (m) { + if (m.message.includes("NS_ERROR_DOM_BAD_URI")) { + ok(true, "drop was blocked"); + resolve(); + } + } + }; + Services.console.registerListener(consoleListener); + registerCleanupFunction(function () { + Services.console.unregisterListener(consoleListener); + }); + + executeSoon(function () { + info("Attempting second drop, of a javascript: URI"); + // The drop handler throws an exception when dragging URIs that inherit + // principal, e.g. javascript: + expectUncaughtException(); + EventUtils.synthesizeDrop(dragSrcElement, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window); + }); + }); + } + + yield* drop([[{type: "text/plain", + data: "http://mochi.test:8888/"}]], + "http://mochi.test:8888/"); + yield* drop([[{type: "text/plain", + data: "http://mochi.test:8888/\nhttp://mochi.test:8888/b\nhttp://mochi.test:8888/c"}]], + "http://mochi.test:8888/|http://mochi.test:8888/b|http://mochi.test:8888/c"); + yield dropInvalidURI(); +}); + diff --git a/browser/base/content/test/general/browser_identity_UI.js b/browser/base/content/test/general/browser_identity_UI.js new file mode 100644 index 000000000..5aacb2e79 --- /dev/null +++ b/browser/base/content/test/general/browser_identity_UI.js @@ -0,0 +1,146 @@ +/* Tests for correct behaviour of getEffectiveHost on identity handler */ + +function test() { + waitForExplicitFinish(); + requestLongerTimeout(2); + + ok(gIdentityHandler, "gIdentityHandler should exist"); + + BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true).then(() => { + gBrowser.selectedBrowser.addEventListener("load", checkResult, true); + nextTest(); + }); +} + +// Greek IDN for 'example.test'. +var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE"; +var tests = [ + { + name: "normal domain", + location: "http://test1.example.org/", + effectiveHost: "test1.example.org" + }, + { + name: "view-source", + location: "view-source:http://example.com/", + effectiveHost: null + }, + { + name: "normal HTTPS", + location: "https://example.com/", + effectiveHost: "example.com", + isHTTPS: true + }, + { + name: "IDN subdomain", + location: "http://sub1." + idnDomain + "/", + effectiveHost: "sub1." + idnDomain + }, + { + name: "subdomain with port", + location: "http://sub1.test1.example.org:8000/", + effectiveHost: "sub1.test1.example.org" + }, + { + name: "subdomain HTTPS", + location: "https://test1.example.com/", + effectiveHost: "test1.example.com", + isHTTPS: true + }, + { + name: "view-source HTTPS", + location: "view-source:https://example.com/", + effectiveHost: null, + isHTTPS: true + }, + { + name: "IP address", + location: "http://127.0.0.1:8888/", + effectiveHost: "127.0.0.1" + }, +] + +var gCurrentTest, gCurrentTestIndex = -1, gTestDesc, gPopupHidden; +// Go through the tests in both directions, to add additional coverage for +// transitions between different states. +var gForward = true; +var gCheckETLD = false; +function nextTest() { + if (!gCheckETLD) { + if (gForward) + gCurrentTestIndex++; + else + gCurrentTestIndex--; + + if (gCurrentTestIndex == tests.length) { + // Went too far, reverse + gCurrentTestIndex--; + gForward = false; + } + + if (gCurrentTestIndex == -1) { + gBrowser.selectedBrowser.removeEventListener("load", checkResult, true); + gBrowser.removeCurrentTab(); + finish(); + return; + } + + gCurrentTest = tests[gCurrentTestIndex]; + gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + ")"; + if (!gForward) + gTestDesc += " (second time)"; + if (gCurrentTest.isHTTPS) { + gCheckETLD = true; + } + + // Navigate to the next page, which will cause checkResult to fire. + let spec = gBrowser.selectedBrowser.currentURI.spec; + if (spec == "about:blank" || spec == gCurrentTest.location) { + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location); + } else { + // Open the Control Center and make sure it closes after nav (Bug 1207542). + let popupShown = promisePopupShown(gIdentityHandler._identityPopup); + gPopupHidden = promisePopupHidden(gIdentityHandler._identityPopup); + gIdentityHandler._identityBox.click(); + info("Waiting for the Control Center to be shown"); + popupShown.then(() => { + is_element_visible(gIdentityHandler._identityPopup, "Control Center is visible"); + // Show the subview, which is an easy way in automation to reproduce + // Bug 1207542, where the CC wouldn't close on navigation. + gBrowser.ownerDocument.querySelector("#identity-popup-security-expander").click(); + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location); + }); + } + } else { + gCheckETLD = false; + gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + " without eTLD in identity icon label)"; + if (!gForward) + gTestDesc += " (second time)"; + gBrowser.selectedBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE | + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY); + } +} + +function checkResult() { + // Sanity check other values, and the value of gIdentityHandler.getEffectiveHost() + is(gIdentityHandler._uri.spec, gCurrentTest.location, "location matches for test " + gTestDesc); + // getEffectiveHost can't be called for all modes + if (gCurrentTest.effectiveHost === null) { + let identityBox = document.getElementById("identity-box"); + ok(identityBox.className == "unknownIdentity" || + identityBox.className == "chromeUI", "mode matched"); + } else { + is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc); + } + + if (gPopupHidden) { + info("Waiting for the Control Center to hide"); + gPopupHidden.then(() => { + gPopupHidden = null; + is_element_hidden(gIdentityHandler._identityPopup, "control center is hidden"); + executeSoon(nextTest); + }); + } else { + executeSoon(nextTest); + } +} diff --git a/browser/base/content/test/general/browser_insecureLoginForms.js b/browser/base/content/test/general/browser_insecureLoginForms.js new file mode 100644 index 000000000..72db7dbe6 --- /dev/null +++ b/browser/base/content/test/general/browser_insecureLoginForms.js @@ -0,0 +1,162 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Load directly from the browser-chrome support files of login tests. +const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/"; + +/** + * Waits for the given number of occurrences of InsecureLoginFormsStateChange + * on the given browser element. + */ +function waitForInsecureLoginFormsStateChange(browser, count) { + return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange", + false, () => --count == 0); +} + +/** + * Checks the insecure login forms logic for the identity block. + */ +add_task(function* test_simple() { + yield new Promise(resolve => SpecialPowers.pushPrefEnv({ + "set": [["security.insecure_password.ui.enabled", true]], + }, resolve)); + + for (let [origin, expectWarning] of [ + ["http://example.com", true], + ["http://127.0.0.1", false], + ["https://example.com", false], + ]) { + let testUrlPath = origin + TEST_URL_PATH; + let tab = gBrowser.addTab(testUrlPath + "form_basic.html"); + let browser = tab.linkedBrowser; + yield Promise.all([ + BrowserTestUtils.switchTab(gBrowser, tab), + BrowserTestUtils.browserLoaded(browser), + // One event is triggered by pageshow and one by DOMFormHasPassword. + waitForInsecureLoginFormsStateChange(browser, 2), + ]); + + let { gIdentityHandler } = gBrowser.ownerGlobal; + gIdentityHandler._identityBox.click(); + document.getElementById("identity-popup-security-expander").click(); + + if (expectWarning) { + is_element_visible(document.getElementById("connection-icon")); + let connectionIconImage = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("connection-icon"), "") + .getPropertyValue("list-style-image"); + let securityViewBG = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-popup-securityView"), "") + .getPropertyValue("background-image"); + let securityContentBG = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-popup-security-content"), "") + .getPropertyValue("background-image"); + is(connectionIconImage, + "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg#icon\")", + "Using expected icon image in the identity block"); + is(securityViewBG, + "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")", + "Using expected icon image in the Control Center main view"); + is(securityContentBG, + "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")", + "Using expected icon image in the Control Center subview"); + is(Array.filter(document.querySelectorAll("[observes=identity-popup-insecure-login-forms-learn-more]"), + element => !is_hidden(element)).length, 1, + "The 'Learn more' link should be visible once."); + } + + // Messages should be visible when the scheme is HTTP, and invisible when + // the scheme is HTTPS. + is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"), + element => !is_hidden(element)), + expectWarning, + "The relevant messages should be visible or hidden."); + + gIdentityHandler._identityPopup.hidden = true; + gBrowser.removeTab(tab); + } +}); + +/** + * Checks that the insecure login forms logic does not regress mixed content + * blocking messages when mixed active content is loaded. + */ +add_task(function* test_mixedcontent() { + yield new Promise(resolve => SpecialPowers.pushPrefEnv({ + "set": [["security.mixed_content.block_active_content", false]], + }, resolve)); + + // Load the page with the subframe in a new tab. + let testUrlPath = "://example.com" + TEST_URL_PATH; + let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html"); + let browser = tab.linkedBrowser; + yield Promise.all([ + BrowserTestUtils.switchTab(gBrowser, tab), + BrowserTestUtils.browserLoaded(browser), + // Two events are triggered by pageshow and one by DOMFormHasPassword. + waitForInsecureLoginFormsStateChange(browser, 3), + ]); + + assertMixedContentBlockingState(browser, { activeLoaded: true, + activeBlocked: false, + passiveLoaded: false }); + + gBrowser.removeTab(tab); +}); + +/** + * Checks that insecure window.opener does not trigger a warning. + */ +add_task(function* test_ignoring_window_opener() { + let newTabURL = "https://example.com" + TEST_URL_PATH + "form_basic.html"; + let path = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com"); + let url = path + "insecure_opener.html"; + + yield BrowserTestUtils.withNewTab(url, function*(browser) { + // Clicking the link will spawn a new tab. + let loaded = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + yield ContentTask.spawn(browser, {}, function() { + content.document.getElementById("link").click(); + }); + let tab = yield loaded; + browser = tab.linkedBrowser; + yield waitForInsecureLoginFormsStateChange(browser, 2); + + // Open the identity popup. + let { gIdentityHandler } = gBrowser.ownerGlobal; + gIdentityHandler._identityBox.click(); + document.getElementById("identity-popup-security-expander").click(); + + ok(is_visible(document.getElementById("connection-icon")), + "Connection icon is visible"); + + // Assert that the identity indicators are still "secure". + let connectionIconImage = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("connection-icon")) + .getPropertyValue("list-style-image"); + let securityViewBG = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-popup-securityView")) + .getPropertyValue("background-image"); + let securityContentBG = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-popup-security-content")) + .getPropertyValue("background-image"); + is(connectionIconImage, + "url(\"chrome://browser/skin/connection-secure.svg\")", + "Using expected icon image in the identity block"); + is(securityViewBG, + "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")", + "Using expected icon image in the Control Center main view"); + is(securityContentBG, + "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")", + "Using expected icon image in the Control Center subview"); + + ok(Array.every(document.querySelectorAll("[when-loginforms=insecure]"), + element => is_hidden(element)), + "All messages should be hidden."); + + gIdentityHandler._identityPopup.hidden = true; + + yield BrowserTestUtils.removeTab(tab); + }); +}); diff --git a/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js new file mode 100644 index 000000000..8e69e781b --- /dev/null +++ b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js @@ -0,0 +1,39 @@ +"use strict"; + + +/** + * Verify that loading an invalid URI does not clobber a previously-loaded page's history + * entry, but that the invalid URI gets its own history entry instead. We're checking this + * using nsIWebNavigation's canGoBack, as well as actually going back and then checking + * canGoForward. + */ +add_task(function* checkBackFromInvalidURI() { + yield pushPrefs(["keyword.enabled", false]); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots", true); + gURLBar.value = "::2600"; + gURLBar.focus(); + + let promiseErrorPageLoaded = new Promise(resolve => { + tab.linkedBrowser.addEventListener("DOMContentLoaded", function onLoad() { + tab.linkedBrowser.removeEventListener("DOMContentLoaded", onLoad, false, true); + resolve(); + }, false, true); + }); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield promiseErrorPageLoaded; + + ok(gBrowser.webNavigation.canGoBack, "Should be able to go back"); + if (gBrowser.webNavigation.canGoBack) { + // Can't use DOMContentLoaded here because the page is bfcached. Can't use pageshow for + // the error page because it doesn't seem to fire for those. + let promiseOtherPageLoaded = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "pageshow", false, + // Be paranoid we *are* actually seeing this other page load, not some kind of race + // for if/when we do start firing pageshow for the error page... + function(e) { return gBrowser.currentURI.spec == "about:robots" } + ); + gBrowser.goBack(); + yield promiseOtherPageLoaded; + ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward from previous page."); + } + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_keywordBookmarklets.js b/browser/base/content/test/general/browser_keywordBookmarklets.js new file mode 100644 index 000000000..5e94733fe --- /dev/null +++ b/browser/base/content/test/general/browser_keywordBookmarklets.js @@ -0,0 +1,54 @@ +"use strict" + +add_task(function* test_keyword_bookmarklet() { + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "bookmarklet", + url: "javascript:'1';" }); + let tab = gBrowser.selectedTab = gBrowser.addTab(); + registerCleanupFunction (function* () { + gBrowser.removeTab(tab); + yield PlacesUtils.bookmarks.remove(bm); + }); + yield promisePageShow(); + let originalPrincipal = gBrowser.contentPrincipal; + + function getPrincipalURI() { + return ContentTask.spawn(tab.linkedBrowser, null, function() { + return content.document.nodePrincipal.URI.spec; + }); + } + + let originalPrincipalURI = yield getPrincipalURI(); + + yield PlacesUtils.keywords.insert({ keyword: "bm", url: "javascript:'1';" }) + + // Enter bookmarklet keyword in the URL bar + gURLBar.value = "bm"; + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + + yield promisePageShow(); + + let newPrincipalURI = yield getPrincipalURI(); + is(newPrincipalURI, originalPrincipalURI, "content has the same principal"); + + // In e10s, null principals don't round-trip so the same null principal sent + // from the child will be a new null principal. Verify that this is the + // case. + if (tab.linkedBrowser.isRemoteBrowser) { + ok(originalPrincipal.isNullPrincipal && gBrowser.contentPrincipal.isNullPrincipal, + "both principals should be null principals in the parent"); + } else { + ok(gBrowser.contentPrincipal.equals(originalPrincipal), + "javascript bookmarklet should inherit principal"); + } +}); + +function* promisePageShow() { + return new Promise(resolve => { + gBrowser.selectedBrowser.addEventListener("pageshow", function listen() { + gBrowser.selectedBrowser.removeEventListener("pageshow", listen); + resolve(); + }); + }); +} diff --git a/browser/base/content/test/general/browser_keywordSearch.js b/browser/base/content/test/general/browser_keywordSearch.js new file mode 100644 index 000000000..cf8bd0c0e --- /dev/null +++ b/browser/base/content/test/general/browser_keywordSearch.js @@ -0,0 +1,88 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + **/ + +var gTests = [ + { + name: "normal search (search service)", + testText: "test search", + searchURL: Services.search.defaultEngine.getSubmission("test search", null, "keyword").uri.spec + }, + { + name: "?-prefixed search (search service)", + testText: "? foo ", + searchURL: Services.search.defaultEngine.getSubmission("foo", null, "keyword").uri.spec + } +]; + +function test() { + waitForExplicitFinish(); + + let windowObserver = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + ok(false, "Alert window opened"); + let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget); + win.addEventListener("load", function() { + win.removeEventListener("load", arguments.callee, false); + win.close(); + }, false); + executeSoon(finish); + } + } + }; + + Services.ww.registerNotification(windowObserver); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + + let listener = { + onStateChange: function onLocationChange(webProgress, req, flags, status) { + // Only care about document starts + let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | + Ci.nsIWebProgressListener.STATE_START; + if (!(flags & docStart)) + return; + + info("received document start"); + + ok(req instanceof Ci.nsIChannel, "req is a channel"); + is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded"); + info("Actual URI: " + req.URI.spec); + + req.cancel(Components.results.NS_ERROR_FAILURE); + + executeSoon(nextTest); + } + }; + gBrowser.addProgressListener(listener); + + registerCleanupFunction(function () { + Services.ww.unregisterNotification(windowObserver); + + gBrowser.removeProgressListener(listener); + gBrowser.removeTab(tab); + }); + + nextTest(); +} + +var gCurrTest; +function nextTest() { + if (gTests.length) { + gCurrTest = gTests.shift(); + doTest(); + } else { + finish(); + } +} + +function doTest() { + info("Running test: " + gCurrTest.name); + + // Simulate a user entering search terms + gURLBar.value = gCurrTest.testText; + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); +} diff --git a/browser/base/content/test/general/browser_keywordSearch_postData.js b/browser/base/content/test/general/browser_keywordSearch_postData.js new file mode 100644 index 000000000..3f700fa58 --- /dev/null +++ b/browser/base/content/test/general/browser_keywordSearch_postData.js @@ -0,0 +1,94 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + **/ + +var gTests = [ + { + name: "normal search (search service)", + testText: "test search", + expectText: "test+search" + }, + { + name: "?-prefixed search (search service)", + testText: "? foo ", + expectText: "foo" + } +]; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + + let searchObserver = function search_observer(aSubject, aTopic, aData) { + let engine = aSubject.QueryInterface(Ci.nsISearchEngine); + info("Observer: " + aData + " for " + engine.name); + + if (aData != "engine-added") + return; + + if (engine.name != "POST Search") + return; + + Services.search.defaultEngine = engine; + + registerCleanupFunction(function () { + Services.search.removeEngine(engine); + }); + + // ready to execute the tests! + executeSoon(nextTest); + }; + + Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); + + registerCleanupFunction(function () { + gBrowser.removeTab(tab); + + Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); + }); + + Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml", + null, null, false); +} + +var gCurrTest; +function nextTest() { + if (gTests.length) { + gCurrTest = gTests.shift(); + doTest(); + } else { + finish(); + } +} + +function doTest() { + info("Running test: " + gCurrTest.name); + + waitForLoad(function () { + let loadedText = gBrowser.contentDocument.body.textContent; + ok(loadedText, "search page loaded"); + let needle = "searchterms=" + gCurrTest.expectText; + is(loadedText, needle, "The query POST data should be returned in the response"); + nextTest(); + }); + + // Simulate a user entering search terms + gURLBar.value = gCurrTest.testText; + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); +} + + +function waitForLoad(cb) { + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function listener() { + if (browser.currentURI.spec == "about:blank") + return; + info("Page loaded: " + browser.currentURI.spec); + browser.removeEventListener("load", listener, true); + + cb(); + }, true); +} diff --git a/browser/base/content/test/general/browser_lastAccessedTab.js b/browser/base/content/test/general/browser_lastAccessedTab.js new file mode 100644 index 000000000..57bd330ae --- /dev/null +++ b/browser/base/content/test/general/browser_lastAccessedTab.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// gBrowser.selectedTab.lastAccessed and Date.now() called from this test can't +// run concurrently, and therefore don't always match exactly. +const CURRENT_TIME_TOLERANCE_MS = 15; + +function isCurrent(tab, msg) { + const DIFF = Math.abs(Date.now() - tab.lastAccessed); + ok(DIFF <= CURRENT_TIME_TOLERANCE_MS, msg + " (difference: " + DIFF + ")"); +} + +function nextStep(fn) { + setTimeout(fn, CURRENT_TIME_TOLERANCE_MS + 10); +} + +var originalTab; +var newTab; + +function test() { + waitForExplicitFinish(); + + originalTab = gBrowser.selectedTab; + nextStep(step2); +} + +function step2() { + isCurrent(originalTab, "selected tab has the current timestamp"); + newTab = gBrowser.addTab("about:blank", {skipAnimation: true}); + nextStep(step3); +} + +function step3() { + ok(newTab.lastAccessed < Date.now(), "new tab hasn't been selected so far"); + gBrowser.selectedTab = newTab; + isCurrent(newTab, "new tab has the current timestamp after being selected"); + nextStep(step4); +} + +function step4() { + ok(originalTab.lastAccessed < Date.now(), + "original tab has old timestamp after being deselected"); + isCurrent(newTab, "new tab has the current timestamp since it's still selected"); + + gBrowser.removeTab(newTab); + finish(); +} diff --git a/browser/base/content/test/general/browser_mcb_redirect.js b/browser/base/content/test/general/browser_mcb_redirect.js new file mode 100644 index 000000000..41b4e9468 --- /dev/null +++ b/browser/base/content/test/general/browser_mcb_redirect.js @@ -0,0 +1,314 @@ +/* + * Description of the Tests for + * - Bug 418354 - Call Mixed content blocking on redirects + * + * Single redirect script tests + * 1. Load a script over https inside an https page + * - the server responds with a 302 redirect to a >> HTTP << script + * - the doorhanger should appear! + * + * 2. Load a script over https inside an http page + * - the server responds with a 302 redirect to a >> HTTP << script + * - the doorhanger should not appear! + * + * Single redirect image tests + * 3. Load an image over https inside an https page + * - the server responds with a 302 redirect to a >> HTTP << image + * - the image should not load + * + * 4. Load an image over https inside an http page + * - the server responds with a 302 redirect to a >> HTTP << image + * - the image should load and get cached + * + * Single redirect cached image tests + * 5. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an http page + * - the server would have responded with a 302 redirect to a >> HTTP << + * image, but instead we try to use the cached image. + * - the image should load + * + * 6. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an https page + * - the server would have responded with a 302 redirect to a >> HTTP << + * image, but instead we try to use the cached image. + * - the image should not load + * + * Double redirect image test + * 7. Load an image over https inside an http page + * - the server responds with a 302 redirect to a >> HTTP << server + * - the HTTP server responds with a 302 redirect to a >> HTTPS << image + * - the image should load and get cached + * + * Double redirect cached image tests + * 8. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an http page + * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS, + * but instead we try to use the cached image. + * - the image should load + * + * 9. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an https page + * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS, + * but instead we try to use the cached image. + * - the image should not load + */ + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const gHttpsTestRoot = "https://example.com/browser/browser/base/content/test/general/"; +const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/"; + +var origBlockActive; +var origBlockDisplay; +var gTestBrowser = null; + +// ------------------------ Helper Functions --------------------- + +registerCleanupFunction(function() { + // Set preferences back to their original values + Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive); + Services.prefs.setBoolPref(PREF_DISPLAY, origBlockDisplay); + + // Make sure we are online again + Services.io.offline = false; +}); + +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); + finish(); +} + +function waitForCondition(condition, nextTest, errorMsg, okMsg) { + var tries = 0; + var interval = setInterval(function() { + if (tries >= 30) { + ok(false, errorMsg); + moveOn(); + } + if (condition()) { + ok(true, okMsg) + moveOn(); + } + tries++; + }, 500); + var moveOn = function() { + clearInterval(interval); nextTest(); + }; +} + +// ------------------------ Test 1 ------------------------------ + +function test1() { + gTestBrowser.addEventListener("load", checkUIForTest1, true); + var url = gHttpsTestRoot + "test_mcb_redirect.html" + gTestBrowser.contentWindow.location = url; +} + +function checkUIForTest1() { + gTestBrowser.removeEventListener("load", checkUIForTest1, true); + + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); + + var expected = "script blocked"; + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test2, "Error: Waited too long for status in Test 1!", + "OK: Expected result in innerHTML for Test1!"); +} + +// ------------------------ Test 2 ------------------------------ + +function test2() { + gTestBrowser.addEventListener("load", checkUIForTest2, true); + var url = gHttpTestRoot + "test_mcb_redirect.html" + gTestBrowser.contentWindow.location = url; +} + +function checkUIForTest2() { + gTestBrowser.removeEventListener("load", checkUIForTest2, true); + + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false}); + + var expected = "script executed"; + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test3, "Error: Waited too long for status in Test 2!", + "OK: Expected result in innerHTML for Test2!"); +} + +// ------------------------ Test 3 ------------------------------ +// HTTPS page loading insecure image +function test3() { + gTestBrowser.addEventListener("load", checkLoadEventForTest3, true); + var url = gHttpsTestRoot + "test_mcb_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest3() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest3, true); + + var expected = "image blocked" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test4, "Error: Waited too long for status in Test 3!", + "OK: Expected result in innerHTML for Test3!"); +} + +// ------------------------ Test 4 ------------------------------ +// HTTP page loading insecure image +function test4() { + gTestBrowser.addEventListener("load", checkLoadEventForTest4, true); + var url = gHttpTestRoot + "test_mcb_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest4() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest4, true); + + var expected = "image loaded" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test5, "Error: Waited too long for status in Test 4!", + "OK: Expected result in innerHTML for Test4!"); +} + +// ------------------------ Test 5 ------------------------------ +// HTTP page laoding insecure cached image +// Assuming test 4 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test5() { + gTestBrowser.addEventListener("load", checkLoadEventForTest5, true); + // Go into offline mode + Services.io.offline = true; + var url = gHttpTestRoot + "test_mcb_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest5() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest5, true); + + var expected = "image loaded" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test6, "Error: Waited too long for status in Test 5!", + "OK: Expected result in innerHTML for Test5!"); + // Go back online + Services.io.offline = false; +} + +// ------------------------ Test 6 ------------------------------ +// HTTPS page loading insecure cached image +// Assuming test 4 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test6() { + gTestBrowser.addEventListener("load", checkLoadEventForTest6, true); + // Go into offline mode + Services.io.offline = true; + var url = gHttpsTestRoot + "test_mcb_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest6() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest6, true); + + var expected = "image blocked" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test7, "Error: Waited too long for status in Test 6!", + "OK: Expected result in innerHTML for Test6!"); + // Go back online + Services.io.offline = false; +} + +// ------------------------ Test 7 ------------------------------ +// HTTP page loading insecure image that went through a double redirect +function test7() { + gTestBrowser.addEventListener("load", checkLoadEventForTest7, true); + var url = gHttpTestRoot + "test_mcb_double_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest7() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest7, true); + + var expected = "image loaded" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test8, "Error: Waited too long for status in Test 7!", + "OK: Expected result in innerHTML for Test7!"); +} + +// ------------------------ Test 8 ------------------------------ +// HTTP page loading insecure cached image that went through a double redirect +// Assuming test 7 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test8() { + gTestBrowser.addEventListener("load", checkLoadEventForTest8, true); + // Go into offline mode + Services.io.offline = true; + var url = gHttpTestRoot + "test_mcb_double_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest8() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest8, true); + + var expected = "image loaded" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + test9, "Error: Waited too long for status in Test 8!", + "OK: Expected result in innerHTML for Test8!"); + // Go back online + Services.io.offline = false; +} + +// ------------------------ Test 9 ------------------------------ +// HTTPS page loading insecure cached image that went through a double redirect +// Assuming test 7 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test9() { + gTestBrowser.addEventListener("load", checkLoadEventForTest9, true); + // Go into offline mode + Services.io.offline = true; + var url = gHttpsTestRoot + "test_mcb_double_redirect_image.html" + gTestBrowser.contentWindow.location = url; +} + +function checkLoadEventForTest9() { + gTestBrowser.removeEventListener("load", checkLoadEventForTest9, true); + + var expected = "image blocked" + waitForCondition( + () => content.document.getElementById('mctestdiv').innerHTML == expected, + cleanUpAfterTests, "Error: Waited too long for status in Test 9!", + "OK: Expected result in innerHTML for Test9!"); + // Go back online + Services.io.offline = false; +} + +// ------------------------ SETUP ------------------------------ + +function test() { + // Performing async calls, e.g. 'onload', we have to wait till all of them finished + waitForExplicitFinish(); + + // Store original preferences so we can restore settings after testing + origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE); + origBlockDisplay = Services.prefs.getBoolPref(PREF_DISPLAY); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + Services.prefs.setBoolPref(PREF_DISPLAY, true); + + pushPrefs(["dom.ipc.processCount", 1]).then(() => { + var newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop(); + + executeSoon(test1); + }); +} diff --git a/browser/base/content/test/general/browser_menuButtonBadgeManager.js b/browser/base/content/test/general/browser_menuButtonBadgeManager.js new file mode 100644 index 000000000..9afe39ab7 --- /dev/null +++ b/browser/base/content/test/general/browser_menuButtonBadgeManager.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var menuButton = document.getElementById("PanelUI-menu-button"); + +add_task(function* testButtonActivities() { + is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status"); + is(menuButton.hasAttribute("badge"), false, "Should not have the badge attribute set"); + + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication"); + is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status"); + + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded"); + is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)"); + + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-failed"); + is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status"); + + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-severe"); + is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status"); + + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-warning"); + is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status"); + + gMenuButtonBadgeManager.addBadge("unknownbadge", "attr"); + is(menuButton.getAttribute("badge-status"), "download-warning", "Should not have changed badge status"); + + gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD); + is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status"); + + gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE); + is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status"); + + gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA); + is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status"); + + yield PanelUI.show(); + is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)"); + PanelUI.hide(); + + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication"); + gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded"); + gMenuButtonBadgeManager.clearBadges(); + is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (clearBadges called)"); +}); diff --git a/browser/base/content/test/general/browser_menuButtonFitts.js b/browser/base/content/test/general/browser_menuButtonFitts.js new file mode 100644 index 000000000..e2541b925 --- /dev/null +++ b/browser/base/content/test/general/browser_menuButtonFitts.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function test () { + waitForExplicitFinish(); + window.maximize(); + + // Find where the nav-bar is vertically. + var navBar = document.getElementById("nav-bar"); + var boundingRect = navBar.getBoundingClientRect(); + var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2); + var xPixel = boundingRect.width - 1; // Use the last pixel of the screen since it is maximized. + + function onPopupHidden() { + PanelUI.panel.removeEventListener("popuphidden", onPopupHidden); + window.restore(); + finish(); + } + function onPopupShown() { + PanelUI.panel.removeEventListener("popupshown", onPopupShown); + ok(true, "Clicking at the far edge of the window opened the menu popup."); + PanelUI.panel.addEventListener("popuphidden", onPopupHidden); + PanelUI.hide(); + } + registerCleanupFunction(function() { + PanelUI.panel.removeEventListener("popupshown", onPopupShown); + PanelUI.panel.removeEventListener("popuphidden", onPopupHidden); + }); + PanelUI.panel.addEventListener("popupshown", onPopupShown); + EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window); +} diff --git a/browser/base/content/test/general/browser_middleMouse_noJSPaste.js b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js new file mode 100644 index 000000000..fa0c26f78 --- /dev/null +++ b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const middleMousePastePref = "middlemouse.contentLoadURL"; +const autoScrollPref = "general.autoScroll"; + +add_task(function* () { + yield pushPrefs([middleMousePastePref, true], [autoScrollPref, false]); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + let url = "javascript:http://www.example.com/"; + yield new Promise((resolve, reject) => { + SimpleTest.waitForClipboard(url, () => { + Components.classes["@mozilla.org/widget/clipboardhelper;1"] + .getService(Components.interfaces.nsIClipboardHelper) + .copyString(url); + }, resolve, () => { + ok(false, "Clipboard copy failed"); + reject(); + }); + }); + + let middlePagePromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + // Middle click on the content area + info("Middle clicking"); + yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {button: 1}, gBrowser.selectedBrowser); + yield middlePagePromise; + + is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS"); + + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_minimize.js b/browser/base/content/test/general/browser_minimize.js new file mode 100644 index 000000000..1d761c0da --- /dev/null +++ b/browser/base/content/test/general/browser_minimize.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function *() { + registerCleanupFunction(function() { + window.restore(); + }); + function waitForActive() { return gBrowser.selectedTab.linkedBrowser.docShellIsActive; } + function waitForInactive() { return !gBrowser.selectedTab.linkedBrowser.docShellIsActive; } + yield promiseWaitForCondition(waitForActive); + is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, true, "Docshell should be active"); + window.minimize(); + yield promiseWaitForCondition(waitForInactive); + is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, false, "Docshell should be Inactive"); + window.restore(); + yield promiseWaitForCondition(waitForActive); + is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, true, "Docshell should be active again"); +}); diff --git a/browser/base/content/test/general/browser_misused_characters_in_strings.js b/browser/base/content/test/general/browser_misused_characters_in_strings.js new file mode 100644 index 000000000..fe8022662 --- /dev/null +++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js @@ -0,0 +1,244 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* This list allows pre-existing or 'unfixable' issues to remain, while we + * detect newly occurring issues in shipping files. It is a list of objects + * specifying conditions under which an error should be ignored. + * + * As each issue is found in the whitelist, it is removed from the list. At + * the end of the test, there is an assertion that all items have been + * removed from the whitelist, thus ensuring there are no stale entries. */ +let gWhitelist = [{ + file: "search.properties", + key: "searchForSomethingWith", + type: "single-quote" + }, { + file: "netError.dtd", + key: "certerror.introPara", + type: "single-quote" + }, { + file: "netError.dtd", + key: "weakCryptoAdvanced.longDesc", + type: "single-quote" + }, { + file: "netError.dtd", + key: "weakCryptoAdvanced.override", + type: "single-quote" + }, { + file: "netError.dtd", + key: "inadequateSecurityError.longDesc", + type: "single-quote" + }, { + file: "netError.dtd", + key: "certerror.wrongSystemTime", + type: "single-quote" + }, { + file: "phishing-afterload-warning-message.dtd", + key: "safeb.blocked.malwarePage.shortDesc", + type: "single-quote" + }, { + file: "phishing-afterload-warning-message.dtd", + key: "safeb.blocked.unwantedPage.shortDesc", + type: "single-quote" + }, { + file: "phishing-afterload-warning-message.dtd", + key: "safeb.blocked.phishingPage.shortDesc2", + type: "single-quote" + }, { + file: "mathfont.properties", + key: "operator.\\u002E\\u002E\\u002E.postfix", + type: "ellipsis" + }, { + file: "layout_errors.properties", + key: "ImageMapRectBoundsError", + type: "double-quote" + }, { + file: "layout_errors.properties", + key: "ImageMapCircleWrongNumberOfCoords", + type: "double-quote" + }, { + file: "layout_errors.properties", + key: "ImageMapCircleNegativeRadius", + type: "double-quote" + }, { + file: "layout_errors.properties", + key: "ImageMapPolyWrongNumberOfCoords", + type: "double-quote" + }, { + file: "layout_errors.properties", + key: "ImageMapPolyOddNumberOfCoords", + type: "double-quote" + }, { + file: "xbl.properties", + key: "CommandNotInChrome", + type: "double-quote" + }, { + file: "dom.properties", + key: "PatternAttributeCompileFailure", + type: "single-quote" + }, { + file: "pipnss.properties", + key: "certErrorMismatchSingle2", + type: "double-quote" + }, { + file: "pipnss.properties", + key: "certErrorCodePrefix2", + type: "double-quote" + }, { + file: "aboutSupport.dtd", + key: "aboutSupport.pageSubtitle", + type: "single-quote" + }, { + file: "aboutSupport.dtd", + key: "aboutSupport.userJSDescription", + type: "single-quote" + }, { + file: "netError.dtd", + key: "inadequateSecurityError.longDesc", + type: "single-quote" + }, { + file: "netErrorApp.dtd", + key: "securityOverride.warningContent", + type: "single-quote" + }, { + file: "pocket.properties", + key: "tos", + type: "double-quote" + }, { + file: "pocket.properties", + key: "tos", + type: "apostrophe" + }, { + file: "aboutNetworking.dtd", + key: "aboutNetworking.logTutorial", + type: "single-quote" + } +]; + +var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm"); +var {generateURIsFromDirTree} = Cu.import(moduleLocation, {}); + +/** + * Check if an error should be ignored due to matching one of the whitelist + * objects defined in gWhitelist. + * + * @param filepath The URI spec of the locale file + * @param key The key of the entity that is being checked + * @param type The type of error that has been found + * @return true if the error should be ignored, false otherwise. + */ +function ignoredError(filepath, key, type) { + for (let index in gWhitelist) { + let whitelistItem = gWhitelist[index]; + if (filepath.endsWith(whitelistItem.file) && + key == whitelistItem.key && + type == whitelistItem.type) { + gWhitelist.splice(index, 1); + return true; + } + } + return false; +} + +function fetchFile(uri) { + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.open("GET", uri, true); + xhr.onreadystatechange = function() { + if (this.readyState != this.DONE) { + return; + } + try { + resolve(this.responseText); + } catch (ex) { + ok(false, `Script error reading ${uri}: ${ex}`); + resolve(""); + } + }; + xhr.onerror = error => { + ok(false, `XHR error reading ${uri}: ${error}`); + resolve(""); + }; + xhr.send(null); + }); +} + +function testForError(filepath, key, str, pattern, type, helpText) { + if (str.match(pattern) && + !ignoredError(filepath, key, type)) { + ok(false, `${filepath} with key=${key} has a misused ${type}. ${helpText}`); + } +} + +function testForErrors(filepath, key, str) { + testForError(filepath, key, str, /\w'\w/, "apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo's."); + testForError(filepath, key, str, /\w\u2018\w/, "incorrect-apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo\u2018s."); + testForError(filepath, key, str, /'.+'/, "single-quote", "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'."); + testForError(filepath, key, str, /"/, "double-quote", "Double-quoted strings should use Unicode \u201cfoo\u201d instead of \"foo\"."); + testForError(filepath, key, str, /\.\.\./, "ellipsis", "Strings with an ellipsis should use the Unicode \u2026 character instead of three periods."); +} + +function* getAllTheFiles(extension) { + let appDirGreD = Services.dirsvc.get("GreD", Ci.nsIFile); + let appDirXCurProcD = Services.dirsvc.get("XCurProcD", Ci.nsIFile); + if (appDirGreD.contains(appDirXCurProcD)) { + return yield generateURIsFromDirTree(appDirGreD, [extension]); + } + if (appDirXCurProcD.contains(appDirGreD)) { + return yield generateURIsFromDirTree(appDirXCurProcD, [extension]); + } + let urisGreD = yield generateURIsFromDirTree(appDirGreD, [extension]); + let urisXCurProcD = yield generateURIsFromDirTree(appDirXCurProcD, [extension]); + return Array.from(new Set(urisGreD.concat(appDirXCurProcD))); +} + +add_task(function* checkAllTheProperties() { + // This asynchronously produces a list of URLs (sadly, mostly sync on our + // test infrastructure because it runs against jarfiles there, and + // our zipreader APIs are all sync) + let uris = yield getAllTheFiles(".properties"); + ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`); + + for (let uri of uris) { + let bundle = new StringBundle(uri.spec); + let entities = bundle.getAll(); + for (let entity of entities) { + testForErrors(uri.spec, entity.key, entity.value); + } + } +}); + +var checkDTD = Task.async(function* (aURISpec) { + let rawContents = yield fetchFile(aURISpec); + // The regular expression below is adapted from: + // https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233 + let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g); + if (!entities) { + // Some files, such as requestAutocomplete.dtd, have no entities defined. + return; + } + for (let entity of entities) { + let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/); + // The matched string includes the enclosing quotation marks, + // we need to slice them off. + str = str.slice(1, -1); + testForErrors(aURISpec, key, str); + } +}); + +add_task(function* checkAllTheDTDs() { + let uris = yield getAllTheFiles(".dtd"); + ok(uris.length, `Found ${uris.length} .dtd files to scan for misused characters`); + for (let uri of uris) { + yield checkDTD(uri.spec); + } + + // This support DTD file supplies a string with a newline to make sure + // the regex in checkDTD works correctly for that case. + let dtdLocation = gTestPath.replace(/\/[^\/]*$/i, "/bug1262648_string_with_newlines.dtd"); + yield checkDTD(dtdLocation); +}); + +add_task(function* ensureWhiteListIsEmpty() { + is(gWhitelist.length, 0, "No remaining whitelist entries exist"); +}); diff --git a/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js new file mode 100644 index 000000000..ac19efd05 --- /dev/null +++ b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js @@ -0,0 +1,34 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Test for Bug 1182551 - + * + * This test has a top level HTTP page with an HTTPS iframe. The HTTPS iframe + * includes an HTTP image. We check that the top level security state is + * STATE_IS_INSECURE. The mixed content from the iframe shouldn't "upgrade" + * the HTTP top level page to broken HTTPS. + */ + +const gHttpTestUrl = "http://example.com/browser/browser/base/content/test/general/file_mixedContentFramesOnHttp.html"; + +var gTestBrowser = null; + +add_task(function *() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({ + "set": [ + ["security.mixed_content.block_active_content", true], + ["security.mixed_content.block_display_content", false] + ] + }, resolve); + }); + let url = gHttpTestUrl + yield BrowserTestUtils.withNewTab({gBrowser, url}, function*() { + gTestBrowser = gBrowser.selectedBrowser; + // check security state is insecure + isSecurityState("insecure"); + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: true}); + }); +}); + diff --git a/browser/base/content/test/general/browser_mixedContentFromOnunload.js b/browser/base/content/test/general/browser_mixedContentFromOnunload.js new file mode 100644 index 000000000..9b39776f4 --- /dev/null +++ b/browser/base/content/test/general/browser_mixedContentFromOnunload.js @@ -0,0 +1,49 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Tests for Bug 947079 - Fix bug in nsSecureBrowserUIImpl that sets the wrong + * security state on a page because of a subresource load that is not on the + * same page. + */ + +// We use different domains for each test and for navigation within each test +const gHttpTestRoot1 = "http://example.com/browser/browser/base/content/test/general/"; +const gHttpsTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/"; +const gHttpTestRoot2 = "http://example.net/browser/browser/base/content/test/general/"; +const gHttpsTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/"; + +var gTestBrowser = null; +add_task(function *() { + let url = gHttpTestRoot1 + "file_mixedContentFromOnunload.html"; + yield BrowserTestUtils.withNewTab({gBrowser, url}, function*() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({ + "set": [ + ["security.mixed_content.block_active_content", true], + ["security.mixed_content.block_display_content", false] + ] + }, resolve); + }); + gTestBrowser = gBrowser.selectedBrowser; + // Navigation from an http page to a https page with no mixed content + // The http page loads an http image on unload + url = gHttpsTestRoot1 + "file_mixedContentFromOnunload_test1.html"; + yield BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); + // check security state. Since current url is https and doesn't have any + // mixed content resources, we expect it to be secure. + isSecurityState("secure"); + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false}); + // Navigation from an http page to a https page that has mixed display content + // The https page loads an http image on unload + url = gHttpTestRoot2 + "file_mixedContentFromOnunload.html"; + yield BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); + url = gHttpsTestRoot2 + "file_mixedContentFromOnunload_test2.html"; + yield BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); + isSecurityState("broken"); + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: true}); + }); +}); diff --git a/browser/base/content/test/general/browser_mixed_content_cert_override.js b/browser/base/content/test/general/browser_mixed_content_cert_override.js new file mode 100644 index 000000000..037fce5d2 --- /dev/null +++ b/browser/base/content/test/general/browser_mixed_content_cert_override.js @@ -0,0 +1,54 @@ +/* + * Bug 1253771 - check mixed content blocking in combination with overriden certificates + */ + +"use strict"; + +const MIXED_CONTENT_URL = "https://self-signed.example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html"; + +function getConnectionState() { + return document.getElementById("identity-popup").getAttribute("connection"); +} + +function getPopupContentVerifier() { + return document.getElementById("identity-popup-content-verifier"); +} + +function getConnectionIcon() { + return window.getComputedStyle(document.getElementById("connection-icon")).listStyleImage; +} + +function checkIdentityPopup(icon) { + gIdentityHandler.refreshIdentityPopup(); + is(getConnectionIcon(), `url("chrome://browser/skin/${icon}")`); + is(getConnectionState(), "secure-cert-user-overridden"); + isnot(getPopupContentVerifier().style.display, "none", "Overridden certificate warning is shown"); + ok(getPopupContentVerifier().textContent.includes("security exception"), "Text shows overridden certificate warning."); +} + +add_task(function* () { + yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + // check that a warning is shown when loading a page with mixed content and an overridden certificate + yield loadBadCertPage(MIXED_CONTENT_URL); + checkIdentityPopup("connection-mixed-passive-loaded.svg#icon"); + + // check that the crossed out icon is shown when disabling mixed content protection + gIdentityHandler.disableMixedContentProtection(); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + checkIdentityPopup("connection-mixed-active-loaded.svg#icon"); + + // check that a warning is shown even without mixed content + yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://self-signed.example.com"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + checkIdentityPopup("connection-mixed-passive-loaded.svg#icon"); + + // remove cert exception + let certOverrideService = Cc["@mozilla.org/security/certoverride;1"] + .getService(Ci.nsICertOverrideService); + certOverrideService.clearValidityOverride("self-signed.example.com", -1); + + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + diff --git a/browser/base/content/test/general/browser_mixedcontent_securityflags.js b/browser/base/content/test/general/browser_mixedcontent_securityflags.js new file mode 100644 index 000000000..1c2614b86 --- /dev/null +++ b/browser/base/content/test/general/browser_mixedcontent_securityflags.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The test loads a web page with mixed active and mixed display content and +// makes sure that the mixed content flags on the docshell are set correctly. +// * Using default about:config prefs (mixed active blocked, mixed display +// loaded) we load the page and check the flags. +// * We change the about:config prefs (mixed active blocked, mixed display +// blocked), reload the page, and check the flags again. +// * We override protection so all mixed content can load and check the +// flags again. + +const TEST_URI = "https://example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html"; +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +var gTestBrowser = null; + +registerCleanupFunction(function() { + // Set preferences back to their original values + Services.prefs.clearUserPref(PREF_DISPLAY); + Services.prefs.clearUserPref(PREF_ACTIVE); + gBrowser.removeCurrentTab(); +}); + +add_task(function* blockMixedActiveContentTest() { + // Turn on mixed active blocking and mixed display loading and load the page. + Services.prefs.setBoolPref(PREF_DISPLAY, false); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); + gTestBrowser = gBrowser.getBrowserForTab(tab); + + yield ContentTask.spawn(gTestBrowser, null, function() { + is(docShell.hasMixedDisplayContentBlocked, false, "hasMixedDisplayContentBlocked flag has been set"); + is(docShell.hasMixedActiveContentBlocked, true, "hasMixedActiveContentBlocked flag has been set"); + is(docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has been set"); + is(docShell.hasMixedActiveContentLoaded, false, "hasMixedActiveContentLoaded flag has been set"); + }); + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: true}); + + // Turn on mixed active and mixed display blocking and reload the page. + Services.prefs.setBoolPref(PREF_DISPLAY, true); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + gBrowser.reload(); + yield BrowserTestUtils.browserLoaded(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, null, function() { + is(docShell.hasMixedDisplayContentBlocked, true, "hasMixedDisplayContentBlocked flag has been set"); + is(docShell.hasMixedActiveContentBlocked, true, "hasMixedActiveContentBlocked flag has been set"); + is(docShell.hasMixedDisplayContentLoaded, false, "hasMixedDisplayContentLoaded flag has been set"); + is(docShell.hasMixedActiveContentLoaded, false, "hasMixedActiveContentLoaded flag has been set"); + }); + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false}); +}); + +add_task(function* overrideMCB() { + // Disable mixed content blocking (reloads page) and retest + let {gIdentityHandler} = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + yield BrowserTestUtils.browserLoaded(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, null, function() { + is(docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has not been set"); + is(docShell.hasMixedActiveContentLoaded, true, "hasMixedActiveContentLoaded flag has not been set"); + is(docShell.hasMixedDisplayContentBlocked, false, "second hasMixedDisplayContentBlocked flag has been set"); + is(docShell.hasMixedActiveContentBlocked, false, "second hasMixedActiveContentBlocked flag has been set"); + }); + assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: true}); +}); diff --git a/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js new file mode 100644 index 000000000..3b5a5a149 --- /dev/null +++ b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js @@ -0,0 +1,30 @@ +"use strict"; + +const kURL = + "http://example.com/browser/browser/base/content/test/general/dummy_page.html"; + "data:text/html,<a href=''>Middle-click me</a>"; + +/* + * Check that when manually opening content JS links in new tabs/windows, + * we use the correct principal, and we don't clear the URL bar. + */ +add_task(function* () { + yield BrowserTestUtils.withNewTab(kURL, function* (browser) { + let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + yield ContentTask.spawn(browser, null, function* () { + let a = content.document.createElement("a"); + a.href = "javascript:document.write('spoof'); void(0);"; + a.textContent = "Some link"; + content.document.body.appendChild(a); + }); + info("Added element"); + yield BrowserTestUtils.synthesizeMouseAtCenter("a", {button: 1}, browser); + let newTab = yield newTabPromise; + is(newTab.linkedBrowser.contentPrincipal.origin, "http://example.com", + "Principal should be for example.com"); + yield BrowserTestUtils.switchTab(gBrowser, newTab); + info(gURLBar.value); + isnot(gURLBar.value, "", "URL bar should not be empty."); + yield BrowserTestUtils.removeTab(newTab); + }); +}); diff --git a/browser/base/content/test/general/browser_newTabDrop.js b/browser/base/content/test/general/browser_newTabDrop.js new file mode 100644 index 000000000..03c90df3f --- /dev/null +++ b/browser/base/content/test/general/browser_newTabDrop.js @@ -0,0 +1,99 @@ +registerCleanupFunction(function* cleanup() { + while (gBrowser.tabs.length > 1) { + yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + } + Services.search.currentEngine = originalEngine; + let engine = Services.search.getEngineByName("MozSearch"); + Services.search.removeEngine(engine); +}); + +let originalEngine; +add_task(function* test_setup() { + // Stop search-engine loads from hitting the network + Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", + "http://example.com/?q={searchTerms}"); + let engine = Services.search.getEngineByName("MozSearch"); + originalEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; +}); + +// New Tab Button opens any link. +add_task(function*() { yield dropText("mochi.test/first", 1); }); +add_task(function*() { yield dropText("javascript:'bad'", 1); }); +add_task(function*() { yield dropText("jAvascript:'bad'", 1); }); +add_task(function*() { yield dropText("mochi.test/second", 1); }); +add_task(function*() { yield dropText("data:text/html,bad", 1); }); +add_task(function*() { yield dropText("mochi.test/third", 1); }); + +// Single text/plain item, with multiple links. +add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); }); +add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 2); }); +add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 2); }); + +// Multiple text/plain items, with single and multiple links. +add_task(function*() { + yield drop([[{type: "text/plain", + data: "mochi.test/5"}], + [{type: "text/plain", + data: "mochi.test/6\nmochi.test/7"}]], 3); +}); + +// Single text/x-moz-url item, with multiple links. +// "text/x-moz-url" has titles in even-numbered lines. +add_task(function*() { + yield drop([[{type: "text/x-moz-url", + data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2); +}); + +// Single item with multiple types. +add_task(function*() { + yield drop([[{type: "text/plain", + data: "mochi.test/10"}, + {type: "text/x-moz-url", + data: "mochi.test/11\nTITLE11"}]], 1); +}); + +function dropText(text, expectedTabOpenCount=0) { + return drop([[{type: "text/plain", data: text}]], expectedTabOpenCount); +} + +function* drop(dragData, expectedTabOpenCount=0) { + let dragDataString = JSON.stringify(dragData); + info(`Starting test for datagData:${dragDataString}; expectedTabOpenCount:${expectedTabOpenCount}`); + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + // Since synthesizeDrop triggers the srcElement, need to use another button. + let dragSrcElement = document.getElementById("downloads-button"); + ok(dragSrcElement, "Downloads button exists"); + let newTabButton = document.getElementById("new-tab-button"); + ok(newTabButton, "New Tab button exists"); + + let awaitDrop = BrowserTestUtils.waitForEvent(newTabButton, "drop"); + let actualTabOpenCount = 0; + let openedTabs = []; + let checkCount = function(event) { + openedTabs.push(event.target); + actualTabOpenCount++; + return actualTabOpenCount == expectedTabOpenCount; + }; + let awaitTabOpen = expectedTabOpenCount && BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", false, checkCount); + + EventUtils.synthesizeDrop(dragSrcElement, newTabButton, dragData, "link", window); + + let tabsOpened = false; + if (awaitTabOpen) { + yield awaitTabOpen; + info("Got TabOpen event"); + tabsOpened = true; + for (let tab of openedTabs) { + yield BrowserTestUtils.removeTab(tab); + } + } + is(tabsOpened, !!expectedTabOpenCount, `Tabs for ${dragDataString} should only open if any of dropped items are valid`); + + yield awaitDrop; + ok(true, "Got drop event"); +} diff --git a/browser/base/content/test/general/browser_newWindowDrop.js b/browser/base/content/test/general/browser_newWindowDrop.js new file mode 100644 index 000000000..f404d4eed --- /dev/null +++ b/browser/base/content/test/general/browser_newWindowDrop.js @@ -0,0 +1,120 @@ +registerCleanupFunction(function* cleanup() { + Services.search.currentEngine = originalEngine; + let engine = Services.search.getEngineByName("MozSearch"); + Services.search.removeEngine(engine); +}); + +let originalEngine; +add_task(function* test_setup() { + // Opening multiple windows on debug build takes too long time. + requestLongerTimeout(10); + + // Stop search-engine loads from hitting the network + Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", + "http://example.com/?q={searchTerms}"); + let engine = Services.search.getEngineByName("MozSearch"); + originalEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + + // Move New Window button to nav bar, to make it possible to drag and drop. + let {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {}); + let origPlacement = CustomizableUI.getPlacementOfWidget("new-window-button"); + if (!origPlacement || origPlacement.area != CustomizableUI.AREA_NAVBAR) { + CustomizableUI.addWidgetToArea("new-window-button", + CustomizableUI.AREA_NAVBAR, + 0); + CustomizableUI.ensureWidgetPlacedInWindow("new-window-button", window); + registerCleanupFunction(function () { + CustomizableUI.reset(); + }); + } +}); + +// New Window Button opens any link. +add_task(function*() { yield dropText("mochi.test/first", 1); }); +add_task(function*() { yield dropText("javascript:'bad'", 1); }); +add_task(function*() { yield dropText("jAvascript:'bad'", 1); }); +add_task(function*() { yield dropText("mochi.test/second", 1); }); +add_task(function*() { yield dropText("data:text/html,bad", 1); }); +add_task(function*() { yield dropText("mochi.test/third", 1); }); + +// Single text/plain item, with multiple links. +add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); }); +add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 2); }); +add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 2); }); + +// Multiple text/plain items, with single and multiple links. +add_task(function*() { + yield drop([[{type: "text/plain", + data: "mochi.test/5"}], + [{type: "text/plain", + data: "mochi.test/6\nmochi.test/7"}]], 3); +}); + +// Single text/x-moz-url item, with multiple links. +// "text/x-moz-url" has titles in even-numbered lines. +add_task(function*() { + yield drop([[{type: "text/x-moz-url", + data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2); +}); + +// Single item with multiple types. +add_task(function*() { + yield drop([[{type: "text/plain", + data: "mochi.test/10"}, + {type: "text/x-moz-url", + data: "mochi.test/11\nTITLE11"}]], 1); +}); + +function dropText(text, expectedWindowOpenCount=0) { + return drop([[{type: "text/plain", data: text}]], expectedWindowOpenCount); +} + +function* drop(dragData, expectedWindowOpenCount=0) { + let dragDataString = JSON.stringify(dragData); + info(`Starting test for datagData:${dragDataString}; expectedWindowOpenCount:${expectedWindowOpenCount}`); + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + // Since synthesizeDrop triggers the srcElement, need to use another button. + let dragSrcElement = document.getElementById("downloads-button"); + ok(dragSrcElement, "Downloads button exists"); + let newWindowButton = document.getElementById("new-window-button"); + ok(newWindowButton, "New Window button exists"); + + let tmp = {}; + Cu.import("resource://testing-common/TestUtils.jsm", tmp); + + let awaitDrop = BrowserTestUtils.waitForEvent(newWindowButton, "drop"); + let actualWindowOpenCount = 0; + let openedWindows = []; + let checkCount = function(window) { + // Add observer as soon as domWindow is opened to avoid missing the topic. + let awaitStartup = tmp.TestUtils.topicObserved("browser-delayed-startup-finished", + subject => subject == window); + openedWindows.push([window, awaitStartup]); + actualWindowOpenCount++; + return actualWindowOpenCount == expectedWindowOpenCount; + }; + let awaitWindowOpen = expectedWindowOpenCount && BrowserTestUtils.domWindowOpened(null, checkCount); + + EventUtils.synthesizeDrop(dragSrcElement, newWindowButton, dragData, "link", window); + + let windowsOpened = false; + if (awaitWindowOpen) { + yield awaitWindowOpen; + info("Got Window opened"); + windowsOpened = true; + for (let [window, awaitStartup] of openedWindows.reverse()) { + // Wait for startup before closing, to properly close the browser window. + yield awaitStartup; + yield BrowserTestUtils.closeWindow(window); + } + } + is(windowsOpened, !!expectedWindowOpenCount, `Windows for ${dragDataString} should only open if any of dropped items are valid`); + + yield awaitDrop; + ok(true, "Got drop event"); +} diff --git a/browser/base/content/test/general/browser_newwindow_focus.js b/browser/base/content/test/general/browser_newwindow_focus.js new file mode 100644 index 000000000..7880db0bd --- /dev/null +++ b/browser/base/content/test/general/browser_newwindow_focus.js @@ -0,0 +1,96 @@ +"use strict"; + +/** + * These tests are for the auto-focus behaviour on the initial browser + * when a window is opened from content. + */ + +const PAGE = `data:text/html,<a id="target" href="%23" onclick="window.open('http://www.example.com', '_blank', 'width=100,height=100');">Click me</a>`; + +/** + * Returns a Promise that resolves when a new window has + * opened, and the "load" event has fired in that window. + * We can't use BrowserTestUtils.domWindowOpened directly, + * because by the time the "then" on the Promise runs, + * DOMContentLoaded and load may have already run in the new + * window. However, we want to be very explicit about what + * events we're waiting for, and not rely on a quirk of our + * Promises infrastructure. + */ +function promiseNewWindow() { + return new Promise((resolve) => { + let observer = (subject, topic, data) => { + if (topic == "domwindowopened") { + Services.ww.unregisterNotification(observer); + let win = subject.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + resolve(win); + }); + } + }; + + Services.ww.registerNotification(observer); + }); +} + +/** + * Test that when a new window is opened from content, focus moves + * to the initial browser in that window once the window has finished + * painting. + */ +add_task(function* test_focus_browser() { + yield BrowserTestUtils.withNewTab({ + url: PAGE, + gBrowser, + }, function*(browser) { + let newWinPromise = promiseNewWindow(); + let delayedStartupPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser); + let newWin = yield newWinPromise; + yield BrowserTestUtils.contentPainted(newWin.gBrowser.selectedBrowser); + yield delayedStartupPromise; + + let focusedElement = + Services.focus.getFocusedElementForWindow(newWin, false, {}); + + Assert.equal(focusedElement, newWin.gBrowser.selectedBrowser, + "Initial browser should be focused"); + + yield BrowserTestUtils.closeWindow(newWin); + }); +}); + +/** + * Test that when a new window is opened from content and focus + * shifts in that window before the content has a chance to paint + * that we _don't_ steal focus once content has painted. + */ +add_task(function* test_no_steal_focus() { + yield BrowserTestUtils.withNewTab({ + url: PAGE, + gBrowser, + }, function*(browser) { + let newWinPromise = promiseNewWindow(); + let delayedStartupPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser); + let newWin = yield newWinPromise; + + // Because we're switching focus, we shouldn't steal it once + // content paints. + newWin.gURLBar.focus(); + + yield BrowserTestUtils.contentPainted(newWin.gBrowser.selectedBrowser); + yield delayedStartupPromise; + + let focusedElement = + Services.focus.getFocusedElementForWindow(newWin, false, {}); + + Assert.equal(focusedElement, newWin.gURLBar.inputField, + "URLBar should be focused"); + + yield BrowserTestUtils.closeWindow(newWin); + }); +}); diff --git a/browser/base/content/test/general/browser_no_mcb_on_http_site.js b/browser/base/content/test/general/browser_no_mcb_on_http_site.js new file mode 100644 index 000000000..45fd67379 --- /dev/null +++ b/browser/base/content/test/general/browser_no_mcb_on_http_site.js @@ -0,0 +1,106 @@ +/* + * Description of the Tests for + * - Bug 909920 - Mixed content warning should not show on a HTTP site + * + * Description of the tests: + * Test 1: + * 1) Load an http page + * 2) The page includes a css file using https + * 3) The css file loads an |IMAGE| << over http + * + * Test 2: + * 1) Load an http page + * 2) The page includes a css file using https + * 3) The css file loads a |FONT| over http + * + * Test 3: + * 1) Load an http page + * 2) The page includes a css file using https + * 3) The css file imports (@import) another css file using http + * 3) The imported css file loads a |FONT| over http +* + * Since the top-domain is >> NOT << served using https, the MCB + * should >> NOT << trigger a warning. + */ + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +const PREF_DISPLAY = "security.mixed_content.block_display_content"; + +const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/"; + +var gTestBrowser = null; + +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); +} + +add_task(function* init() { + yield SpecialPowers.pushPrefEnv({ set: [[ PREF_ACTIVE, true ], + [ PREF_DISPLAY, true ]] }); + let url = gHttpTestRoot + "test_no_mcb_on_http_site_img.html"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url) + gTestBrowser = tab.linkedBrowser; +}); + +// ------------- TEST 1 ----------------------------------------- + +add_task(function* test1() { + let expected = "Verifying MCB does not trigger warning/error for an http page "; + expected += "with https css that includes http image"; + + yield ContentTask.spawn(gTestBrowser, expected, function* (condition) { + yield ContentTaskUtils.waitForCondition( + () => content.document.getElementById("testDiv").innerHTML == condition, + "Waited too long for status in Test 1!"); + }); + + // Explicit OKs needed because the harness requires at least one call to ok. + ok(true, "test 1 passed"); + + // set up test 2 + let url = gHttpTestRoot + "test_no_mcb_on_http_site_font.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +// ------------- TEST 2 ----------------------------------------- + +add_task(function* test2() { + let expected = "Verifying MCB does not trigger warning/error for an http page "; + expected += "with https css that includes http font"; + + yield ContentTask.spawn(gTestBrowser, expected, function* (condition) { + yield ContentTaskUtils.waitForCondition( + () => content.document.getElementById("testDiv").innerHTML == condition, + "Waited too long for status in Test 2!"); + }); + + ok(true, "test 2 passed"); + + // set up test 3 + let url = gHttpTestRoot + "test_no_mcb_on_http_site_font2.html"; + BrowserTestUtils.loadURI(gTestBrowser, url); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +// ------------- TEST 3 ----------------------------------------- + +add_task(function* test3() { + let expected = "Verifying MCB does not trigger warning/error for an http page " + expected += "with https css that imports another http css which includes http font"; + + yield ContentTask.spawn(gTestBrowser, expected, function* (condition) { + yield ContentTaskUtils.waitForCondition( + () => content.document.getElementById("testDiv").innerHTML == condition, + "Waited too long for status in Test 3!"); + }); + + ok(true, "test3 passed"); +}); + +// ------------------------------------------------------ + +add_task(function* cleanup() { + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/general/browser_offlineQuotaNotification.js b/browser/base/content/test/general/browser_offlineQuotaNotification.js new file mode 100644 index 000000000..e56bfe9a8 --- /dev/null +++ b/browser/base/content/test/general/browser_offlineQuotaNotification.js @@ -0,0 +1,95 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test offline quota warnings - must be run as a mochitest-browser test or +// else the test runner gets in the way of notifications due to bug 857897. + +const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/offlineQuotaNotification.html"; + +registerCleanupFunction(function() { + // Clean up after ourself + let uri = Services.io.newURI(URL, null, null); + let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {}); + Services.perms.removeFromPrincipal(principal, "offline-app"); + Services.prefs.clearUserPref("offline-apps.quota.warn"); + Services.prefs.clearUserPref("offline-apps.allow_by_default"); + let {OfflineAppCacheHelper} = Components.utils.import("resource:///modules/offlineAppCache.jsm", {}); + OfflineAppCacheHelper.clear(); +}); + +// Same as the other one, but for in-content preferences +function checkInContentPreferences(win) { + let doc = win.document; + let sel = doc.getElementById("categories").selectedItems[0].id; + let tab = doc.getElementById("advancedPrefs").selectedTab.id; + is(gBrowser.currentURI.spec, "about:preferences#advanced", "about:preferences loaded"); + is(sel, "category-advanced", "Advanced pane was selected"); + is(tab, "networkTab", "Network tab is selected"); + // all good, we are done. + win.close(); + finish(); +} + +function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref("offline-apps.allow_by_default", false); + + // Open a new tab. + gBrowser.selectedTab = gBrowser.addTab(URL); + registerCleanupFunction(() => gBrowser.removeCurrentTab()); + + + Promise.all([ + // Wait for a notification that asks whether to allow offline storage. + promiseNotification(), + // Wait for the tab to load. + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser), + ]).then(() => { + info("Loaded page, adding onCached handler"); + // Need a promise to keep track of when we've added our handler. + let mm = gBrowser.selectedBrowser.messageManager; + let onCachedAttached = BrowserTestUtils.waitForMessage(mm, "Test:OnCachedAttached"); + let gotCached = ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + return new Promise(resolve => { + content.window.applicationCache.oncached = function() { + setTimeout(resolve, 0); + }; + sendAsyncMessage("Test:OnCachedAttached"); + }); + }); + gotCached.then(function() { + // We got cached - now we should have provoked the quota warning. + let notification = PopupNotifications.getNotification('offline-app-usage'); + ok(notification, "have offline-app-usage notification"); + // select the default action - this should cause the preferences + // tab to open - which we track via an "Initialized" event. + PopupNotifications.panel.firstElementChild.button.click(); + let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab); + newTabBrowser.addEventListener("Initialized", function PrefInit() { + newTabBrowser.removeEventListener("Initialized", PrefInit, true); + executeSoon(function() { + checkInContentPreferences(newTabBrowser.contentWindow); + }) + }, true); + }); + onCachedAttached.then(function() { + Services.prefs.setIntPref("offline-apps.quota.warn", 1); + + // Click the notification panel's "Allow" button. This should kick + // off updates which will call our oncached handler above. + PopupNotifications.panel.firstElementChild.button.click(); + }); + }); +} + +function promiseNotification() { + return new Promise(resolve => { + PopupNotifications.panel.addEventListener("popupshown", function onShown() { + PopupNotifications.panel.removeEventListener("popupshown", onShown); + resolve(); + }); + }); +} diff --git a/browser/base/content/test/general/browser_overflowScroll.js b/browser/base/content/test/general/browser_overflowScroll.js new file mode 100644 index 000000000..56932fae2 --- /dev/null +++ b/browser/base/content/test/general/browser_overflowScroll.js @@ -0,0 +1,91 @@ +var tabstrip = gBrowser.tabContainer.mTabstrip; +var scrollbox = tabstrip._scrollbox; +var originalSmoothScroll = tabstrip.smoothScroll; +var tabs = gBrowser.tabs; + +var rect = ele => ele.getBoundingClientRect(); +var width = ele => rect(ele).width; +var left = ele => rect(ele).left; +var right = ele => rect(ele).right; +var isLeft = (ele, msg) => is(left(ele) + tabstrip._tabMarginLeft, left(scrollbox), msg); +var isRight = (ele, msg) => is(right(ele) - tabstrip._tabMarginRight, right(scrollbox), msg); +var elementFromPoint = x => tabstrip._elementFromPoint(x); +var nextLeftElement = () => elementFromPoint(left(scrollbox) - 1); +var nextRightElement = () => elementFromPoint(right(scrollbox) + 1); +var firstScrollable = () => tabs[gBrowser._numPinnedTabs]; + +function test() { + requestLongerTimeout(2); + waitForExplicitFinish(); + + // If the previous (or more) test finished with cleaning up the tabs, + // there may be some pending animations. That can cause a failure of + // this tests, so, we should test this in another stack. + setTimeout(doTest, 0); +} + +function doTest() { + tabstrip.smoothScroll = false; + + var tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth); + var tabCountForOverflow = Math.ceil(width(tabstrip) / tabMinWidth * 3); + while (tabs.length < tabCountForOverflow) + gBrowser.addTab("about:blank", {skipAnimation: true}); + gBrowser.pinTab(tabs[0]); + + tabstrip.addEventListener("overflow", runOverflowTests, false); +} + +function runOverflowTests(aEvent) { + if (aEvent.detail != 1) + return; + + tabstrip.removeEventListener("overflow", runOverflowTests, false); + + var upButton = tabstrip._scrollButtonUp; + var downButton = tabstrip._scrollButtonDown; + var element; + + gBrowser.selectedTab = firstScrollable(); + ok(left(scrollbox) <= left(firstScrollable()), "Selecting the first tab scrolls it into view " + + "(" + left(scrollbox) + " <= " + left(firstScrollable()) + ")"); + + element = nextRightElement(); + EventUtils.synthesizeMouseAtCenter(downButton, {}); + isRight(element, "Scrolled one tab to the right with a single click"); + + gBrowser.selectedTab = tabs[tabs.length - 1]; + ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " + + "(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")"); + + element = nextLeftElement(); + EventUtils.synthesizeMouse(upButton, 1, 1, {}); + isLeft(element, "Scrolled one tab to the left with a single click"); + + let elementPoint = left(scrollbox) - width(scrollbox); + element = elementFromPoint(elementPoint); + if (elementPoint == right(element)) { + element = element.nextSibling; + } + EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2}); + isLeft(element, "Scrolled one page of tabs with a double click"); + + EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 3}); + var firstScrollableLeft = left(firstScrollable()); + ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " + + "(" + left(scrollbox) + " <= " + firstScrollableLeft + ")"); + + for (var i = 2; i; i--) + EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE }); + is(left(firstScrollable()), firstScrollableLeft, "Remained at the start with the mouse wheel"); + + element = nextRightElement(); + EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE}); + isRight(element, "Scrolled one tab to the right with the mouse wheel"); + + while (tabs.length > 1) + gBrowser.removeTab(tabs[0]); + + tabstrip.smoothScroll = originalSmoothScroll; + finish(); +} diff --git a/browser/base/content/test/general/browser_pageInfo.js b/browser/base/content/test/general/browser_pageInfo.js new file mode 100644 index 000000000..90fe2e17f --- /dev/null +++ b/browser/base/content/test/general/browser_pageInfo.js @@ -0,0 +1,38 @@ +function test() { + waitForExplicitFinish(); + + var pageInfo; + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + + Services.obs.addObserver(observer, "page-info-dialog-loaded", false); + pageInfo = BrowserPageInfo(); + }, true); + content.location = + "https://example.com/browser/browser/base/content/test/general/feed_tab.html"; + + function observer(win, topic, data) { + Services.obs.removeObserver(observer, "page-info-dialog-loaded"); + pageInfo.onFinished.push(handlePageInfo); + } + + function handlePageInfo() { + ok(pageInfo.document.getElementById("feedTab"), "Feed tab"); + let feedListbox = pageInfo.document.getElementById("feedListbox"); + ok(feedListbox, "Feed list"); + + var feedRowsNum = feedListbox.getRowCount(); + is(feedRowsNum, 3, "Number of feeds listed"); + + for (var i = 0; i < feedRowsNum; i++) { + let feedItem = feedListbox.getItemAtIndex(i); + is(feedItem.getAttribute("name"), i + 1, "Feed name"); + } + + pageInfo.close(); + gBrowser.removeCurrentTab(); + finish(); + } +} diff --git a/browser/base/content/test/general/browser_page_style_menu.js b/browser/base/content/test/general/browser_page_style_menu.js new file mode 100644 index 000000000..cb080d52a --- /dev/null +++ b/browser/base/content/test/general/browser_page_style_menu.js @@ -0,0 +1,97 @@ +"use strict"; + +/** + * Stylesheets are updated for a browser after the pageshow event. + * This helper function returns a Promise that waits for that pageshow + * event, and then resolves on the next tick to ensure that gPageStyleMenu + * has had a chance to update the stylesheets. + * + * @param browser + * The <xul:browser> to wait for. + * @return Promise + */ +function promiseStylesheetsUpdated(browser) { + return ContentTask.spawn(browser, { PAGE }, function*(args) { + return new Promise((resolve) => { + addEventListener("pageshow", function onPageShow(e) { + if (e.target.location == args.PAGE) { + removeEventListener("pageshow", onPageShow); + content.setTimeout(resolve, 0); + } + }); + }) + }); +} + +const PAGE = "http://example.com/browser/browser/base/content/test/general/page_style_sample.html"; + +/* + * Test that the right stylesheets do (and others don't) show up + * in the page style menu. + */ +add_task(function*() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false); + let browser = tab.linkedBrowser; + yield BrowserTestUtils.loadURI(browser, PAGE); + yield promiseStylesheetsUpdated(browser); + + let menupopup = document.getElementById("pageStyleMenu").menupopup; + gPageStyleMenu.fillPopup(menupopup); + + var items = []; + var current = menupopup.getElementsByTagName("menuseparator")[0]; + while (current.nextSibling) { + current = current.nextSibling; + items.push(current); + } + + items = items.map(el => ({ + label: el.getAttribute("label"), + checked: el.getAttribute("checked") == "true", + })); + + let validLinks = yield ContentTask.spawn(gBrowser.selectedBrowser, items, function(contentItems) { + let contentValidLinks = 0; + Array.forEach(content.document.querySelectorAll("link, style"), function (el) { + var title = el.getAttribute("title"); + var rel = el.getAttribute("rel"); + var media = el.getAttribute("media"); + var idstring = el.nodeName + " " + (title ? title : "without title and") + + " with rel=\"" + rel + "\"" + + (media ? " and media=\"" + media + "\"" : ""); + + var item = contentItems.filter(aItem => aItem.label == title); + var found = item.length == 1; + var checked = found && item[0].checked; + + switch (el.getAttribute("data-state")) { + case "0": + ok(!found, idstring + " should not show up in page style menu"); + break; + case "0-todo": + contentValidLinks++; + todo(!found, idstring + " should not show up in page style menu"); + ok(!checked, idstring + " should not be selected"); + break; + case "1": + contentValidLinks++; + ok(found, idstring + " should show up in page style menu"); + ok(!checked, idstring + " should not be selected"); + break; + case "2": + contentValidLinks++; + ok(found, idstring + " should show up in page style menu"); + ok(checked, idstring + " should be selected"); + break; + default: + throw "data-state attribute is missing or has invalid value"; + } + }); + return contentValidLinks; + }); + + ok(items.length, "At least one item in the menu"); + is(items.length, validLinks, "all valid links found"); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_page_style_menu_update.js b/browser/base/content/test/general/browser_page_style_menu_update.js new file mode 100644 index 000000000..a0c741e48 --- /dev/null +++ b/browser/base/content/test/general/browser_page_style_menu_update.js @@ -0,0 +1,67 @@ +"use strict"; + +const PAGE = "http://example.com/browser/browser/base/content/test/general/page_style_sample.html"; + +/** + * Stylesheets are updated for a browser after the pageshow event. + * This helper function returns a Promise that waits for that pageshow + * event, and then resolves on the next tick to ensure that gPageStyleMenu + * has had a chance to update the stylesheets. + * + * @param browser + * The <xul:browser> to wait for. + * @return Promise + */ +function promiseStylesheetsUpdated(browser) { + return ContentTask.spawn(browser, { PAGE }, function*(args) { + return new Promise((resolve) => { + addEventListener("pageshow", function onPageShow(e) { + if (e.target.location == args.PAGE) { + removeEventListener("pageshow", onPageShow); + content.setTimeout(resolve, 0); + } + }); + }) + }); +} + +/** + * Tests that the Page Style menu shows the currently + * selected Page Style after a new one has been selected. + */ +add_task(function*() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false); + let browser = tab.linkedBrowser; + + yield BrowserTestUtils.loadURI(browser, PAGE); + yield promiseStylesheetsUpdated(browser); + + let menupopup = document.getElementById("pageStyleMenu").menupopup; + gPageStyleMenu.fillPopup(menupopup); + + // page_style_sample.html should default us to selecting the stylesheet + // with the title "6" first. + let selected = menupopup.querySelector("menuitem[checked='true']"); + is(selected.getAttribute("label"), "6", "Should have '6' stylesheet selected by default"); + + // Now select stylesheet "1" + let target = menupopup.querySelector("menuitem[label='1']"); + target.click(); + + // Now we need to wait for the content process to send its stylesheet + // update for the selected tab to the parent. Because messages are + // guaranteed to be sent in order, we'll make sure we do the check + // after the parent has been updated by yielding until the child + // has finished running a ContentTask for us. + yield ContentTask.spawn(browser, {}, function*() { + dump('\nJust wasting some time.\n'); + }); + + gPageStyleMenu.fillPopup(menupopup); + // gPageStyleMenu empties out the menu between opens, so we need + // to get a new reference to the selected menuitem + selected = menupopup.querySelector("menuitem[checked='true']"); + is(selected.getAttribute("label"), "1", "Should now have stylesheet 1 selected"); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_pageinfo_svg_image.js b/browser/base/content/test/general/browser_pageinfo_svg_image.js new file mode 100644 index 000000000..02514d79f --- /dev/null +++ b/browser/base/content/test/general/browser_pageinfo_svg_image.js @@ -0,0 +1,38 @@ +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec, + "mediaTab"); + + pageInfo.addEventListener("load", function loadListener2() { + pageInfo.removeEventListener("load", loadListener2, true); + pageInfo.onFinished.push(function() { + executeSoon(function() { + var imageTree = pageInfo.document.getElementById("imagetree"); + var imageRowsNum = imageTree.view.rowCount; + + ok(imageTree, "Image tree is null (media tab is broken)"); + + is(imageRowsNum, 1, "should have one image"); + + // Only bother running this if we've got the right number of rows. + if (imageRowsNum == 1) { + is(imageTree.view.getCellText(0, imageTree.columns[0]), + "https://example.com/browser/browser/base/content/test/general/title_test.svg", + "The URL should be the svg image."); + } + + pageInfo.close(); + gBrowser.removeCurrentTab(); + finish(); + }); + }); + }, true); + }, true); + + content.location = + "https://example.com/browser/browser/base/content/test/general/svg_image.html"; +} diff --git a/browser/base/content/test/general/browser_parsable_css.js b/browser/base/content/test/general/browser_parsable_css.js new file mode 100644 index 000000000..72954d2e5 --- /dev/null +++ b/browser/base/content/test/general/browser_parsable_css.js @@ -0,0 +1,376 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* This list allows pre-existing or 'unfixable' CSS issues to remain, while we + * detect newly occurring issues in shipping CSS. It is a list of objects + * specifying conditions under which an error should be ignored. + * + * Every property of the objects in it needs to consist of a regular expression + * matching the offending error. If an object has multiple regex criteria, they + * ALL need to match an error in order for that error not to cause a test + * failure. */ +let whitelist = [ + // CodeMirror is imported as-is, see bug 1004423. + {sourceName: /codemirror\.css$/i, + isFromDevTools: true}, + // The debugger uses cross-browser CSS. + {sourceName: /devtools\/client\/debugger\/new\/styles.css/i, + isFromDevTools: true}, + // PDFjs is futureproofing its pseudoselectors, and those rules are dropped. + {sourceName: /web\/viewer\.css$/i, + errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i, + isFromDevTools: false}, + // Tracked in bug 1004428. + {sourceName: /aboutaccounts\/(main|normalize)\.css$/i, + isFromDevTools: false}, + // Highlighter CSS uses a UA-only pseudo-class, see bug 985597. + {sourceName: /highlighters\.css$/i, + errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i, + isFromDevTools: true}, + // Responsive Design Mode CSS uses a UA-only pseudo-class, see Bug 1241714. + {sourceName: /responsive-ua\.css$/i, + errorMessage: /Unknown pseudo-class.*moz-dropdown-list/i, + isFromDevTools: true}, + + {sourceName: /\b(contenteditable|EditorOverride|svg|forms|html|mathml|ua)\.css$/i, + errorMessage: /Unknown pseudo-class.*-moz-/i, + isFromDevTools: false}, + {sourceName: /\b(html|mathml|ua)\.css$/i, + errorMessage: /Unknown property.*-moz-/i, + isFromDevTools: false}, + // Reserved to UA sheets unless layout.css.overflow-clip-box.enabled flipped to true. + {sourceName: /res\/forms\.css$/i, + errorMessage: /Unknown property.*overflow-clip-box/i, + isFromDevTools: false}, + {sourceName: /res\/(ua|html)\.css$/i, + errorMessage: /Unknown pseudo-class .*\bfullscreen\b/i, + isFromDevTools: false}, + {sourceName: /skin\/timepicker\.css$/i, + errorMessage: /Error in parsing.*mask/i, + isFromDevTools: false}, +]; + +// Platform can be "linux", "macosx" or "win". If omitted, the exception applies to all platforms. +let allowedImageReferences = [ + // Bug 1302691 + {file: "chrome://devtools/skin/images/dock-bottom-minimize@2x.png", + from: "chrome://devtools/skin/toolbox.css", + isFromDevTools: true}, + {file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png", + from: "chrome://devtools/skin/toolbox.css", + isFromDevTools: true}, +]; + +var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm"); +var {generateURIsFromDirTree} = Cu.import(moduleLocation, {}); + +// Add suffix to stylesheets' URI so that we always load them here and +// have them parsed. Add a random number so that even if we run this +// test multiple times, it would be unlikely to affect each other. +const kPathSuffix = "?always-parse-css-" + Math.random(); + +/** + * Check if an error should be ignored due to matching one of the whitelist + * objects defined in whitelist + * + * @param aErrorObject the error to check + * @return true if the error should be ignored, false otherwise. + */ +function ignoredError(aErrorObject) { + for (let whitelistItem of whitelist) { + let matches = true; + for (let prop of ["sourceName", "errorMessage"]) { + if (whitelistItem.hasOwnProperty(prop) && + !whitelistItem[prop].test(aErrorObject[prop] || "")) { + matches = false; + break; + } + } + if (matches) { + whitelistItem.used = true; + return true; + } + } + return false; +} + +function once(target, name) { + return new Promise((resolve, reject) => { + let cb = () => { + target.removeEventListener(name, cb); + resolve(); + }; + target.addEventListener(name, cb); + }); +} + +function fetchFile(uri) { + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.responseType = "text"; + xhr.open("GET", uri, true); + xhr.onreadystatechange = function() { + if (this.readyState != this.DONE) { + return; + } + try { + resolve(this.responseText); + } catch (ex) { + ok(false, `Script error reading ${uri}: ${ex}`); + resolve(""); + } + }; + xhr.onerror = error => { + ok(false, `XHR error reading ${uri}: ${error}`); + resolve(""); + }; + xhr.send(null); + }); +} + +var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIChromeRegistry); +var gChromeMap = new Map(); + +function getBaseUriForChromeUri(chromeUri) { + let chromeFile = chromeUri + "gobbledygooknonexistentfile.reallynothere"; + let uri = Services.io.newURI(chromeFile, null, null); + let fileUri = gChromeReg.convertChromeURL(uri); + return fileUri.resolve("."); +} + +function parseManifest(manifestUri) { + return fetchFile(manifestUri.spec).then(data => { + for (let line of data.split('\n')) { + let [type, ...argv] = line.split(/\s+/); + let component; + if (type == "content" || type == "skin") { + [component] = argv; + } else { + // skip unrelated lines + continue; + } + let chromeUri = `chrome://${component}/${type}/`; + gChromeMap.set(getBaseUriForChromeUri(chromeUri), chromeUri); + } + }); +} + +function convertToChromeUri(fileUri) { + let baseUri = fileUri.spec; + let path = ""; + while (true) { + let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2); + if (slashPos < 0) { + info(`File not accessible from chrome protocol: ${fileUri.path}`); + return fileUri; + } + path = baseUri.slice(slashPos + 1) + path; + baseUri = baseUri.slice(0, slashPos + 1); + if (gChromeMap.has(baseUri)) { + let chromeBaseUri = gChromeMap.get(baseUri); + let chromeUri = `${chromeBaseUri}${path}`; + return Services.io.newURI(chromeUri, null, null); + } + } +} + +function messageIsCSSError(msg) { + // Only care about CSS errors generated by our iframe: + if ((msg instanceof Ci.nsIScriptError) && + msg.category.includes("CSS") && + msg.sourceName.endsWith(kPathSuffix)) { + let sourceName = msg.sourceName.slice(0, -kPathSuffix.length); + let msgInfo = { sourceName, errorMessage: msg.errorMessage }; + // Check if this error is whitelisted in whitelist + if (!ignoredError(msgInfo)) { + ok(false, `Got error message for ${sourceName}: ${msg.errorMessage}`); + return true; + } + info(`Ignored error for ${sourceName} because of filter.`); + } + return false; +} + +let imageURIsToReferencesMap = new Map(); + +function processCSSRules(sheet) { + for (let rule of sheet.cssRules) { + if (rule instanceof CSSMediaRule) { + processCSSRules(rule); + continue; + } + if (!(rule instanceof CSSStyleRule)) + continue; + + // Extract urls from the css text. + // Note: CSSStyleRule.cssText always has double quotes around URLs even + // when the original CSS file didn't. + let urls = rule.cssText.match(/url\("[^"]*"\)/g); + if (!urls) + continue; + + for (let url of urls) { + // Remove the url(" prefix and the ") suffix. + url = url.replace(/url\("(.*)"\)/, "$1"); + if (url.startsWith("data:")) + continue; + + // Make the url absolute and remove the ref. + let baseURI = Services.io.newURI(rule.parentStyleSheet.href, null, null); + url = Services.io.newURI(url, null, baseURI).specIgnoringRef; + + // Store the image url along with the css file referencing it. + let baseUrl = baseURI.spec.split("?always-parse-css")[0]; + if (!imageURIsToReferencesMap.has(url)) { + imageURIsToReferencesMap.set(url, new Set([baseUrl])); + } else { + imageURIsToReferencesMap.get(url).add(baseUrl); + } + } + } +} + +function chromeFileExists(aURI) +{ + let available = 0; + try { + let channel = NetUtil.newChannel({uri: aURI, loadUsingSystemPrincipal: true}); + let stream = channel.open(); + let sstream = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sstream.init(stream); + available = sstream.available(); + sstream.close(); + } catch (e) { + if (e.result != Components.results.NS_ERROR_FILE_NOT_FOUND) { + dump("Checking " + aURI + ": " + e + "\n"); + Cu.reportError(e); + } + } + return available > 0; +} + +add_task(function* checkAllTheCSS() { + let appDir = Services.dirsvc.get("GreD", Ci.nsIFile); + // This asynchronously produces a list of URLs (sadly, mostly sync on our + // test infrastructure because it runs against jarfiles there, and + // our zipreader APIs are all sync) + let uris = yield generateURIsFromDirTree(appDir, [".css", ".manifest"]); + + // Create a clean iframe to load all the files into. This needs to live at a + // chrome URI so that it's allowed to load and parse any styles. + let testFile = getRootDirectory(gTestPath) + "dummy_page.html"; + let windowless = Services.appShell.createWindowlessBrowser(); + let iframe = windowless.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe"); + windowless.document.documentElement.appendChild(iframe); + let iframeLoaded = once(iframe, 'load'); + iframe.contentWindow.location = testFile; + yield iframeLoaded; + let doc = iframe.contentWindow.document; + + // Parse and remove all manifests from the list. + // NOTE that this must be done before filtering out devtools paths + // so that all chrome paths can be recorded. + let manifestPromises = []; + uris = uris.filter(uri => { + if (uri.path.endsWith(".manifest")) { + manifestPromises.push(parseManifest(uri)); + return false; + } + return true; + }); + // Wait for all manifest to be parsed + yield Promise.all(manifestPromises); + + // We build a list of promises that get resolved when their respective + // files have loaded and produced no errors. + let allPromises = []; + + // filter out either the devtools paths or the non-devtools paths: + let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools"; + let devtoolsPathBits = ["webide", "devtools"]; + uris = uris.filter(uri => isDevtools == devtoolsPathBits.some(path => uri.spec.includes(path))); + + for (let uri of uris) { + let linkEl = doc.createElement("link"); + linkEl.setAttribute("rel", "stylesheet"); + let promiseForThisSpec = Promise.defer(); + let onLoad = (e) => { + processCSSRules(linkEl.sheet); + promiseForThisSpec.resolve(); + linkEl.removeEventListener("load", onLoad); + linkEl.removeEventListener("error", onError); + }; + let onError = (e) => { + ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!"); + promiseForThisSpec.resolve(); + linkEl.removeEventListener("load", onLoad); + linkEl.removeEventListener("error", onError); + }; + linkEl.addEventListener("load", onLoad); + linkEl.addEventListener("error", onError); + linkEl.setAttribute("type", "text/css"); + let chromeUri = convertToChromeUri(uri); + linkEl.setAttribute("href", chromeUri.spec + kPathSuffix); + allPromises.push(promiseForThisSpec.promise); + doc.head.appendChild(linkEl); + } + + // Wait for all the files to have actually loaded: + yield Promise.all(allPromises); + + // Check if all the files referenced from CSS actually exist. + for (let [image, references] of imageURIsToReferencesMap) { + if (!chromeFileExists(image)) { + for (let ref of references) { + let ignored = false; + for (let item of allowedImageReferences) { + if (image.endsWith(item.file) && ref.endsWith(item.from) && + isDevtools == item.isFromDevTools && + (!item.platforms || item.platforms.includes(AppConstants.platform))) { + item.used = true; + ignored = true; + break; + } + } + if (!ignored) + ok(false, "missing " + image + " referenced from " + ref); + } + } + } + + let messages = Services.console.getMessageArray(); + // Count errors (the test output will list actual issues for us, as well + // as the ok(false) in messageIsCSSError. + let errors = messages.filter(messageIsCSSError); + is(errors.length, 0, "All the styles (" + allPromises.length + ") loaded without errors."); + + // Confirm that all whitelist rules have been used. + for (let item of whitelist) { + if (!item.used && isDevtools == item.isFromDevTools) { + ok(false, "Unused whitelist item. " + + (item.sourceName ? " sourceName: " + item.sourceName : "") + + (item.errorMessage ? " errorMessage: " + item.errorMessage : "")); + } + } + + // Confirm that all file whitelist rules have been used. + for (let item of allowedImageReferences) { + if (!item.used && isDevtools == item.isFromDevTools && + (!item.platforms || item.platforms.includes(AppConstants.platform))) { + ok(false, "Unused file whitelist item. " + + " file: " + item.file + + " from: " + item.from); + } + } + + // Clean up to avoid leaks: + iframe.remove(); + doc.head.innerHTML = ''; + doc = null; + iframe = null; + windowless.close(); + windowless = null; + imageURIsToReferencesMap = null; +}); diff --git a/browser/base/content/test/general/browser_parsable_script.js b/browser/base/content/test/general/browser_parsable_script.js new file mode 100644 index 000000000..50333dd65 --- /dev/null +++ b/browser/base/content/test/general/browser_parsable_script.js @@ -0,0 +1,132 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* This list allows pre-existing or 'unfixable' JS issues to remain, while we + * detect newly occurring issues in shipping JS. It is a list of regexes + * matching files which have errors: + */ +const kWhitelist = new Set([ + /defaults\/profile\/prefs.js$/, + /browser\/content\/browser\/places\/controller.js$/, +]); + + +var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm"); +var {generateURIsFromDirTree} = Cu.import(moduleLocation, {}); + +// Normally we would use reflect.jsm to get Reflect.parse. However, if +// we do that, then all the AST data is allocated in reflect.jsm's +// zone. That exposes a bug in our GC. The GC collects reflect.jsm's +// zone but not the zone in which our test code lives (since no new +// data is being allocated in it). The cross-compartment wrappers in +// our zone that point to the AST data never get collected, and so the +// AST data itself is never collected. We need to GC both zones at +// once to fix the problem. +const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance(); +init(); + +/** + * Check if an error should be ignored due to matching one of the whitelist + * objects defined in kWhitelist + * + * @param uri the uri to check against the whitelist + * @return true if the uri should be skipped, false otherwise. + */ +function uriIsWhiteListed(uri) { + for (let whitelistItem of kWhitelist) { + if (whitelistItem.test(uri.spec)) { + return true; + } + } + return false; +} + +function parsePromise(uri) { + let promise = new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.open("GET", uri, true); + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + let scriptText = this.responseText; + try { + info("Checking " + uri); + Reflect.parse(scriptText); + resolve(true); + } catch (ex) { + let errorMsg = "Script error reading " + uri + ": " + ex; + ok(false, errorMsg); + resolve(false); + } + } + }; + xhr.onerror = (error) => { + ok(false, "XHR error reading " + uri + ": " + error); + resolve(false); + }; + xhr.overrideMimeType("application/javascript"); + xhr.send(null); + }); + return promise; +} + +add_task(function* checkAllTheJS() { + // In debug builds, even on a fast machine, collecting the file list may take + // more than 30 seconds, and parsing all files may take four more minutes. + // For this reason, this test must be explictly requested in debug builds by + // using the "--setpref parse=<filter>" argument to mach. You can specify: + // - A case-sensitive substring of the file name to test (slow). + // - A single absolute URI printed out by a previous run (fast). + // - An empty string to run the test on all files (slowest). + let parseRequested = Services.prefs.prefHasUserValue("parse"); + let parseValue = parseRequested && Services.prefs.getCharPref("parse"); + if (SpecialPowers.isDebugBuild) { + if (!parseRequested) { + ok(true, "Test disabled on debug build. To run, execute: ./mach" + + " mochitest-browser --setpref parse=<case_sensitive_filter>" + + " browser/base/content/test/general/browser_parsable_script.js"); + return; + } + // Request a 15 minutes timeout (30 seconds * 30) for debug builds. + requestLongerTimeout(30); + } + + let uris; + // If an absolute URI is specified on the command line, use it immediately. + if (parseValue && parseValue.includes(":")) { + uris = [NetUtil.newURI(parseValue)]; + } else { + let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile); + // This asynchronously produces a list of URLs (sadly, mostly sync on our + // test infrastructure because it runs against jarfiles there, and + // our zipreader APIs are all sync) + let startTimeMs = Date.now(); + info("Collecting URIs"); + uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]); + info("Collected URIs in " + (Date.now() - startTimeMs) + "ms"); + + // Apply the filter specified on the command line, if any. + if (parseValue) { + uris = uris.filter(uri => { + if (uri.spec.includes(parseValue)) { + return true; + } + info("Not checking filtered out " + uri.spec); + return false; + }); + } + } + + // We create an array of promises so we can parallelize all our parsing + // and file loading activity: + let allPromises = []; + for (let uri of uris) { + if (uriIsWhiteListed(uri)) { + info("Not checking whitelisted " + uri.spec); + continue; + } + allPromises.push(parsePromise(uri.spec)); + } + + let promiseResults = yield Promise.all(allPromises); + is(promiseResults.filter((x) => !x).length, 0, "There should be 0 parsing errors"); +}); diff --git a/browser/base/content/test/general/browser_permissions.js b/browser/base/content/test/general/browser_permissions.js new file mode 100644 index 000000000..721a669d2 --- /dev/null +++ b/browser/base/content/test/general/browser_permissions.js @@ -0,0 +1,202 @@ +/* + * Test the Permissions section in the Control Center. + */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +const PERMISSIONS_PAGE = "http://example.com/browser/browser/base/content/test/general/permissions.html"; +var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {}); + +registerCleanupFunction(function() { + SitePermissions.remove(gBrowser.currentURI, "cookie"); + SitePermissions.remove(gBrowser.currentURI, "geo"); + SitePermissions.remove(gBrowser.currentURI, "camera"); + SitePermissions.remove(gBrowser.currentURI, "microphone"); + + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +function* openIdentityPopup() { + let {gIdentityHandler} = gBrowser.ownerGlobal; + let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown"); + gIdentityHandler._identityBox.click(); + return promise; +} + +function* closeIdentityPopup() { + let {gIdentityHandler} = gBrowser.ownerGlobal; + let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden"); + gIdentityHandler._identityPopup.hidePopup(); + return promise; +} + +add_task(function* testMainViewVisible() { + let tab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); + + let permissionsList = document.getElementById("identity-popup-permission-list"); + let emptyLabel = permissionsList.nextSibling.nextSibling; + + yield openIdentityPopup(); + + ok(!is_hidden(emptyLabel), "List of permissions is empty"); + + yield closeIdentityPopup(); + + SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW); + + yield openIdentityPopup(); + + ok(is_hidden(emptyLabel), "List of permissions is not empty"); + + let labelText = SitePermissions.getPermissionLabel("camera"); + let labels = permissionsList.querySelectorAll(".identity-popup-permission-label"); + is(labels.length, 1, "One permission visible in main view"); + is(labels[0].textContent, labelText, "Correct value"); + + let img = permissionsList.querySelector("image.identity-popup-permission-icon"); + ok(img, "There is an image for the permissions"); + ok(img.classList.contains("camera-icon"), "proper class is in image class"); + + yield closeIdentityPopup(); + + SitePermissions.remove(gBrowser.currentURI, "camera"); + + yield openIdentityPopup(); + + ok(!is_hidden(emptyLabel), "List of permissions is empty"); + + yield closeIdentityPopup(); +}); + +add_task(function* testIdentityIcon() { + let {gIdentityHandler} = gBrowser.ownerGlobal; + let tab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); + + SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW); + + ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"), + "identity-box signals granted permissions"); + + SitePermissions.remove(gBrowser.currentURI, "geo"); + + ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"), + "identity-box doesn't signal granted permissions"); + + SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK); + + ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"), + "identity-box doesn't signal granted permissions"); + + SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.SESSION); + + ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"), + "identity-box signals granted permissions"); + + SitePermissions.remove(gBrowser.currentURI, "geo"); + SitePermissions.remove(gBrowser.currentURI, "camera"); + SitePermissions.remove(gBrowser.currentURI, "cookie"); +}); + +add_task(function* testCancelPermission() { + let tab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); + + let permissionsList = document.getElementById("identity-popup-permission-list"); + let emptyLabel = permissionsList.nextSibling.nextSibling; + + SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW); + SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK); + + yield openIdentityPopup(); + + ok(is_hidden(emptyLabel), "List of permissions is not empty"); + + let cancelButtons = permissionsList + .querySelectorAll(".identity-popup-permission-remove-button"); + + cancelButtons[0].click(); + let labels = permissionsList.querySelectorAll(".identity-popup-permission-label"); + is(labels.length, 1, "One permission should be removed"); + cancelButtons[1].click(); + labels = permissionsList.querySelectorAll(".identity-popup-permission-label"); + is(labels.length, 0, "One permission should be removed"); + + yield closeIdentityPopup(); +}); + +add_task(function* testPermissionHints() { + let tab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); + + let permissionsList = document.getElementById("identity-popup-permission-list"); + let emptyHint = document.getElementById("identity-popup-permission-empty-hint"); + let reloadHint = document.getElementById("identity-popup-permission-reload-hint"); + + yield openIdentityPopup(); + + ok(!is_hidden(emptyHint), "Empty hint is visible"); + ok(is_hidden(reloadHint), "Reload hint is hidden"); + + yield closeIdentityPopup(); + + SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW); + SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK); + + yield openIdentityPopup(); + + ok(is_hidden(emptyHint), "Empty hint is hidden"); + ok(is_hidden(reloadHint), "Reload hint is hidden"); + + let cancelButtons = permissionsList + .querySelectorAll(".identity-popup-permission-remove-button"); + SitePermissions.remove(gBrowser.currentURI, "camera"); + + cancelButtons[0].click(); + ok(is_hidden(emptyHint), "Empty hint is hidden"); + ok(!is_hidden(reloadHint), "Reload hint is visible"); + + cancelButtons[1].click(); + ok(is_hidden(emptyHint), "Empty hint is hidden"); + ok(!is_hidden(reloadHint), "Reload hint is visible"); + + yield closeIdentityPopup(); + yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); + yield openIdentityPopup(); + + ok(!is_hidden(emptyHint), "Empty hint is visible after reloading"); + ok(is_hidden(reloadHint), "Reload hint is hidden after reloading"); + + yield closeIdentityPopup(); +}); + +add_task(function* testPermissionIcons() { + let {gIdentityHandler} = gBrowser.ownerGlobal; + let tab = gBrowser.selectedTab = gBrowser.addTab(); + yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE); + + SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW); + SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK); + SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION); + + let geoIcon = gIdentityHandler._identityBox + .querySelector(".blocked-permission-icon[data-permission-id='geo']"); + ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown"); + + let cameraIcon = gIdentityHandler._identityBox + .querySelector(".blocked-permission-icon[data-permission-id='camera']"); + ok(!cameraIcon.hasAttribute("showing"), + "allowed permission icon is not shown"); + + let microphoneIcon = gIdentityHandler._identityBox + .querySelector(".blocked-permission-icon[data-permission-id='microphone']"); + ok(!microphoneIcon.hasAttribute("showing"), + "allowed permission icon is not shown"); + + SitePermissions.remove(gBrowser.currentURI, "geo"); + + ok(!geoIcon.hasAttribute("showing"), + "blocked permission icon is not shown after reset"); +}); diff --git a/browser/base/content/test/general/browser_pinnedTabs.js b/browser/base/content/test/general/browser_pinnedTabs.js new file mode 100644 index 000000000..e0ddb5072 --- /dev/null +++ b/browser/base/content/test/general/browser_pinnedTabs.js @@ -0,0 +1,75 @@ +var tabs; + +function index(tab) { + return Array.indexOf(gBrowser.tabs, tab); +} + +function indexTest(tab, expectedIndex, msg) { + var diag = "tab " + tab + " should be at index " + expectedIndex; + if (msg) + msg = msg + " (" + diag + ")"; + else + msg = diag; + is(index(tabs[tab]), expectedIndex, msg); +} + +function PinUnpinHandler(tab, eventName) { + this.eventCount = 0; + var self = this; + tab.addEventListener(eventName, function() { + tab.removeEventListener(eventName, arguments.callee, true); + + self.eventCount++; + }, true); + gBrowser.tabContainer.addEventListener(eventName, function(e) { + gBrowser.tabContainer.removeEventListener(eventName, arguments.callee, true); + + if (e.originalTarget == tab) { + self.eventCount++; + } + }, true); +} + +function test() { + tabs = [gBrowser.selectedTab, gBrowser.addTab(), gBrowser.addTab(), gBrowser.addTab()]; + indexTest(0, 0); + indexTest(1, 1); + indexTest(2, 2); + indexTest(3, 3); + + var eh = new PinUnpinHandler(tabs[3], "TabPinned"); + gBrowser.pinTab(tabs[3]); + is(eh.eventCount, 2, "TabPinned event should be fired"); + indexTest(0, 1); + indexTest(1, 2); + indexTest(2, 3); + indexTest(3, 0); + + eh = new PinUnpinHandler(tabs[1], "TabPinned"); + gBrowser.pinTab(tabs[1]); + is(eh.eventCount, 2, "TabPinned event should be fired"); + indexTest(0, 2); + indexTest(1, 1); + indexTest(2, 3); + indexTest(3, 0); + + gBrowser.moveTabTo(tabs[3], 3); + indexTest(3, 1, "shouldn't be able to mix a pinned tab into normal tabs"); + + gBrowser.moveTabTo(tabs[2], 0); + indexTest(2, 2, "shouldn't be able to mix a normal tab into pinned tabs"); + + eh = new PinUnpinHandler(tabs[1], "TabUnpinned"); + gBrowser.unpinTab(tabs[1]); + is(eh.eventCount, 2, "TabUnpinned event should be fired"); + indexTest(1, 1, "unpinning a tab should move a tab to the start of normal tabs"); + + eh = new PinUnpinHandler(tabs[3], "TabUnpinned"); + gBrowser.unpinTab(tabs[3]); + is(eh.eventCount, 2, "TabUnpinned event should be fired"); + indexTest(3, 0, "unpinning a tab should move a tab to the start of normal tabs"); + + gBrowser.removeTab(tabs[1]); + gBrowser.removeTab(tabs[2]); + gBrowser.removeTab(tabs[3]); +} diff --git a/browser/base/content/test/general/browser_plainTextLinks.js b/browser/base/content/test/general/browser_plainTextLinks.js new file mode 100644 index 000000000..7a304fce0 --- /dev/null +++ b/browser/base/content/test/general/browser_plainTextLinks.js @@ -0,0 +1,146 @@ +function testExpected(expected, msg) { + is(document.getElementById("context-openlinkincurrent").hidden, expected, msg); +} + +function testLinkExpected(expected, msg) { + is(gContextMenu.linkURL, expected, msg); +} + +add_task(function *() { + const url = "data:text/html;charset=UTF-8,Test For Non-Hyperlinked url selection"; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW); + + // Initial setup of the content area. + yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) { + let doc = content.document; + let range = doc.createRange(); + let selection = content.getSelection(); + + let mainDiv = doc.createElement("div"); + let div = doc.createElement("div"); + let div2 = doc.createElement("div"); + let span1 = doc.createElement("span"); + let span2 = doc.createElement("span"); + let span3 = doc.createElement("span"); + let span4 = doc.createElement("span"); + let p1 = doc.createElement("p"); + let p2 = doc.createElement("p"); + span1.textContent = "http://index."; + span2.textContent = "example.com example.com"; + span3.textContent = " - Test"; + span4.innerHTML = "<a href='http://www.example.com'>http://www.example.com/example</a>"; + p1.textContent = "mailto:test.com ftp.example.com"; + p2.textContent = "example.com -"; + div.appendChild(span1); + div.appendChild(span2); + div.appendChild(span3); + div.appendChild(span4); + div.appendChild(p1); + div.appendChild(p2); + let p3 = doc.createElement("p"); + p3.textContent = "main.example.com"; + div2.appendChild(p3); + mainDiv.appendChild(div); + mainDiv.appendChild(div2); + doc.body.appendChild(mainDiv); + + function setSelection(el1, el2, index1, index2) { + while (el1.nodeType != el1.TEXT_NODE) + el1 = el1.firstChild; + while (el2.nodeType != el1.TEXT_NODE) + el2 = el2.firstChild; + + selection.removeAllRanges(); + range.setStart(el1, index1); + range.setEnd(el2, index2); + selection.addRange(range); + + return range; + } + + // Each of these tests creates a selection and returns a range within it. + content.tests = [ + () => setSelection(span1.firstChild, span2.firstChild, 0, 11), + () => setSelection(span1.firstChild, span2.firstChild, 7, 11), + () => setSelection(span1.firstChild, span2.firstChild, 8, 11), + () => setSelection(span2.firstChild, span2.firstChild, 0, 11), + () => setSelection(span2.firstChild, span2.firstChild, 11, 23), + () => setSelection(span2.firstChild, span2.firstChild, 0, 10), + () => setSelection(span2.firstChild, span3.firstChild, 12, 7), + () => setSelection(span2.firstChild, span2.firstChild, 12, 19), + () => setSelection(p1.firstChild, p1.firstChild, 0, 15), + () => setSelection(p1.firstChild, p1.firstChild, 16, 31), + () => setSelection(p2.firstChild, p2.firstChild, 0, 14), + () => { + selection.selectAllChildren(div2); + return selection.getRangeAt(0); + }, + () => { + selection.selectAllChildren(span4); + return selection.getRangeAt(0); + }, + () => { + mainDiv.innerHTML = "(open-suse.ru)"; + return setSelection(mainDiv, mainDiv, 1, 13); + }, + () => setSelection(mainDiv, mainDiv, 1, 14) + ]; + }); + + let checks = [ + () => testExpected(false, "The link context menu should show for http://www.example.com"), + () => testExpected(false, "The link context menu should show for www.example.com"), + () => testExpected(true, "The link context menu should not show for ww.example.com"), + () => { + testExpected(false, "The link context menu should show for example.com"); + testLinkExpected("http://example.com/", "url for example.com selection should not prepend www"); + }, + () => testExpected(false, "The link context menu should show for example.com"), + () => testExpected(true, "Link options should not show for selection that's not at a word boundary"), + () => testExpected(true, "Link options should not show for selection that has whitespace"), + () => testExpected(true, "Link options should not show unless a url is selected"), + () => testExpected(true, "Link options should not show for mailto: links"), + () => { + testExpected(false, "Link options should show for ftp.example.com"); + testLinkExpected("http://ftp.example.com/", "ftp.example.com should be preceeded with http://"); + }, + () => testExpected(false, "Link options should show for www.example.com "), + () => testExpected(false, "Link options should show for triple-click selections"), + () => testLinkExpected("http://www.example.com/", "Linkified text should open the correct link"), + () => { + testExpected(false, "Link options should show for open-suse.ru"); + testLinkExpected("http://open-suse.ru/", "Linkified text should open the correct link"); + }, + () => testExpected(true, "Link options should not show for 'open-suse.ru)'") + ]; + + let contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); + + for (let testid = 0; testid < checks.length; testid++) { + let menuPosition = yield ContentTask.spawn(gBrowser.selectedBrowser, { testid: testid }, function* (arg) { + let range = content.tests[arg.testid](); + + // Get the range of the selection and determine its coordinates. These + // coordinates will be returned to the parent process and the context menu + // will be opened at that location. + let rangeRect = range.getBoundingClientRect(); + return [rangeRect.x + 3, rangeRect.y + 3]; + }); + + let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouseAtPoint(menuPosition[0], menuPosition[1], + { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser); + yield popupShownPromise; + + checks[testid](); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden"); + contentAreaContextMenu.hidePopup(); + yield popupHiddenPromise; + } + + gBrowser.removeCurrentTab(); +}); + diff --git a/browser/base/content/test/general/browser_printpreview.js b/browser/base/content/test/general/browser_printpreview.js new file mode 100644 index 000000000..c38fc18be --- /dev/null +++ b/browser/base/content/test/general/browser_printpreview.js @@ -0,0 +1,74 @@ +let ourTab; + +function test() { + waitForExplicitFinish(); + + BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true).then(function(tab) { + ourTab = tab; + ok(!gInPrintPreviewMode, + "Should NOT be in print preview mode at starting this tests"); + // Skip access key test on platforms which don't support access key. + if (!/Win|Linux/.test(navigator.platform)) { + openPrintPreview(testClosePrintPreviewWithEscKey); + } else { + openPrintPreview(testClosePrintPreviewWithAccessKey); + } + }); +} + +function tidyUp() { + BrowserTestUtils.removeTab(ourTab).then(finish); +} + +function testClosePrintPreviewWithAccessKey() { + EventUtils.synthesizeKey("c", { altKey: true }); + checkPrintPreviewClosed(function (aSucceeded) { + ok(aSucceeded, + "print preview mode should be finished by access key"); + openPrintPreview(testClosePrintPreviewWithEscKey); + }); +} + +function testClosePrintPreviewWithEscKey() { + EventUtils.synthesizeKey("VK_ESCAPE", {}); + checkPrintPreviewClosed(function (aSucceeded) { + ok(aSucceeded, + "print preview mode should be finished by Esc key press"); + openPrintPreview(testClosePrintPreviewWithClosingWindowShortcutKey); + }); +} + +function testClosePrintPreviewWithClosingWindowShortcutKey() { + EventUtils.synthesizeKey("w", { accelKey: true }); + checkPrintPreviewClosed(function (aSucceeded) { + ok(aSucceeded, + "print preview mode should be finished by closing window shortcut key"); + tidyUp(); + }); +} + +function openPrintPreview(aCallback) { + document.getElementById("cmd_printPreview").doCommand(); + executeSoon(function () { + if (gInPrintPreviewMode) { + executeSoon(aCallback); + return; + } + executeSoon(arguments.callee); + }); +} + +function checkPrintPreviewClosed(aCallback) { + let count = 0; + executeSoon(function () { + if (!gInPrintPreviewMode) { + executeSoon(function () { aCallback(count < 1000); }); + return; + } + if (++count == 1000) { + // The test might fail. + PrintUtils.exitPrintPreview(); + } + executeSoon(arguments.callee); + }); +} diff --git a/browser/base/content/test/general/browser_private_browsing_window.js b/browser/base/content/test/general/browser_private_browsing_window.js new file mode 100644 index 000000000..607a34060 --- /dev/null +++ b/browser/base/content/test/general/browser_private_browsing_window.js @@ -0,0 +1,65 @@ +// Make sure that we can open private browsing windows + +function test() { + waitForExplicitFinish(); + var nonPrivateWin = OpenBrowserWindow(); + ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow() should open a normal window"); + nonPrivateWin.close(); + + var privateWin = OpenBrowserWindow({private: true}); + ok(PrivateBrowsingUtils.isWindowPrivate(privateWin), "OpenBrowserWindow({private: true}) should open a private window"); + + nonPrivateWin = OpenBrowserWindow({private: false}); + ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow({private: false}) should open a normal window"); + nonPrivateWin.close(); + + whenDelayedStartupFinished(privateWin, function() { + nonPrivateWin = privateWin.OpenBrowserWindow({private: false}); + ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "privateWin.OpenBrowserWindow({private: false}) should open a normal window"); + + nonPrivateWin.close(); + + [ + { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accesskey: true }, + { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accesskey: false }, + ].forEach(function(menu) { + let newWindow = privateWin.document.getElementById(menu.normal); + let newPrivateWindow = privateWin.document.getElementById(menu.private); + if (newWindow && newPrivateWindow) { + ok(!newPrivateWindow.hidden, "New Private Window menu item should be hidden"); + isnot(newWindow.label, newPrivateWindow.label, "New Window's label shouldn't be overwritten"); + if (menu.accesskey) { + isnot(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey shouldn't be overwritten"); + } + isnot(newWindow.command, newPrivateWindow.command, "New Window's command shouldn't be overwritten"); + } + }); + + privateWin.close(); + + Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true); + privateWin = OpenBrowserWindow({private: true}); + whenDelayedStartupFinished(privateWin, function() { + [ + { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accessKey: true }, + { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accessKey: false }, + ].forEach(function(menu) { + let newWindow = privateWin.document.getElementById(menu.normal); + let newPrivateWindow = privateWin.document.getElementById(menu.private); + if (newWindow && newPrivateWindow) { + ok(newPrivateWindow.hidden, "New Private Window menu item should be hidden"); + is(newWindow.label, newPrivateWindow.label, "New Window's label should be overwritten"); + if (menu.accesskey) { + is(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey should be overwritten"); + } + is(newWindow.command, newPrivateWindow.command, "New Window's command should be overwritten"); + } + }); + + privateWin.close(); + Services.prefs.clearUserPref("browser.privatebrowsing.autostart"); + finish(); + }); + }); +} + diff --git a/browser/base/content/test/general/browser_private_no_prompt.js b/browser/base/content/test/general/browser_private_no_prompt.js new file mode 100644 index 000000000..c6c580f80 --- /dev/null +++ b/browser/base/content/test/general/browser_private_no_prompt.js @@ -0,0 +1,12 @@ +function test() { + waitForExplicitFinish(); + var privateWin = OpenBrowserWindow({private: true}); + + whenDelayedStartupFinished(privateWin, function () { + privateWin.BrowserOpenTab(); + privateWin.BrowserTryToCloseWindow(); + ok(true, "didn't prompt"); + + executeSoon(finish); + }); +} diff --git a/browser/base/content/test/general/browser_purgehistory_clears_sh.js b/browser/base/content/test/general/browser_purgehistory_clears_sh.js new file mode 100644 index 000000000..1a1e6554d --- /dev/null +++ b/browser/base/content/test/general/browser_purgehistory_clears_sh.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const url = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + +add_task(function* purgeHistoryTest() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url, + }, function* purgeHistoryTestInner(browser) { + let backButton = browser.ownerDocument.getElementById("Browser:Back"); + let forwardButton = browser.ownerDocument.getElementById("Browser:Forward"); + + ok(!browser.webNavigation.canGoBack, + "Initial value for webNavigation.canGoBack"); + ok(!browser.webNavigation.canGoForward, + "Initial value for webNavigation.canGoBack"); + ok(backButton.hasAttribute("disabled"), "Back button is disabled"); + ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled"); + + yield ContentTask.spawn(browser, null, function*() { + let startHistory = content.history.length; + content.history.pushState({}, ""); + content.history.pushState({}, ""); + content.history.back(); + let newHistory = content.history.length; + Assert.equal(startHistory, 1, "Initial SHistory size"); + Assert.equal(newHistory, 3, "New SHistory size"); + }); + + ok(browser.webNavigation.canGoBack, true, + "New value for webNavigation.canGoBack"); + ok(browser.webNavigation.canGoForward, true, + "New value for webNavigation.canGoForward"); + ok(!backButton.hasAttribute("disabled"), "Back button was enabled"); + ok(!forwardButton.hasAttribute("disabled"), "Forward button was enabled"); + + + let tmp = {}; + Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tmp); + + let {Sanitizer} = tmp; + let sanitizer = new Sanitizer(); + + yield sanitizer.sanitize(["history"]); + + yield ContentTask.spawn(browser, null, function*() { + Assert.equal(content.history.length, 1, "SHistory correctly cleared"); + }); + + ok(!browser.webNavigation.canGoBack, + "webNavigation.canGoBack correctly cleared"); + ok(!browser.webNavigation.canGoForward, + "webNavigation.canGoForward correctly cleared"); + ok(backButton.hasAttribute("disabled"), "Back button was disabled"); + ok(forwardButton.hasAttribute("disabled"), "Forward button was disabled"); + }); +}); diff --git a/browser/base/content/test/general/browser_refreshBlocker.js b/browser/base/content/test/general/browser_refreshBlocker.js new file mode 100644 index 000000000..ee274f2c2 --- /dev/null +++ b/browser/base/content/test/general/browser_refreshBlocker.js @@ -0,0 +1,135 @@ +"use strict"; + +const META_PAGE = "http://example.org/browser/browser/base/content/test/general/refresh_meta.sjs" +const HEADER_PAGE = "http://example.org/browser/browser/base/content/test/general/refresh_header.sjs" +const TARGET_PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; +const PREF = "accessibility.blockautorefresh"; + +/** + * Goes into the content, and simulates a meta-refresh header at a very + * low level, and checks to see if it was blocked. This will always cancel + * the refresh, regardless of whether or not the refresh was blocked. + * + * @param browser (<xul:browser>) + * The browser to test for refreshing. + * @param expectRefresh (bool) + * Whether or not we expect the refresh attempt to succeed. + * @returns Promise + */ +function* attemptFakeRefresh(browser, expectRefresh) { + yield ContentTask.spawn(browser, expectRefresh, function*(contentExpectRefresh) { + let URI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI; + let refresher = docShell.QueryInterface(Ci.nsIRefreshURI); + refresher.refreshURI(URI, 0, false, true); + + Assert.equal(refresher.refreshPending, contentExpectRefresh, + "Got the right refreshPending state"); + + if (refresher.refreshPending) { + // Cancel the pending refresh + refresher.cancelRefreshURITimers(); + } + + // The RefreshBlocker will wait until onLocationChange has + // been fired before it will show any notifications (see bug + // 1246291), so we cause this to occur manually here. + content.location = URI.spec + "#foo"; + }); +} + +/** + * Tests that we can enable the blocking pref and block a refresh + * from occurring while showing a notification bar. Also tests that + * when we disable the pref, that refreshes can go through again. + */ +add_task(function* test_can_enable_and_block() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: TARGET_PAGE, + }, function*(browser) { + // By default, we should be able to reload the page. + yield attemptFakeRefresh(browser, true); + + yield pushPrefs(["accessibility.blockautorefresh", true]); + + let notificationPromise = + BrowserTestUtils.waitForNotificationBar(gBrowser, browser, + "refresh-blocked"); + + yield attemptFakeRefresh(browser, false); + + yield notificationPromise; + + yield pushPrefs(["accessibility.blockautorefresh", false]); + + // Page reloads should go through again. + yield attemptFakeRefresh(browser, true); + }); +}); + +/** + * Attempts a "real" refresh by opening a tab, and then sending it to + * an SJS page that will attempt to cause a refresh. This will also pass + * a delay amount to the SJS page. The refresh should be blocked, and + * the notification should be shown. Once shown, the "Allow" button will + * be clicked, and the refresh will go through. Finally, the helper will + * close the tab and resolve the Promise. + * + * @param refreshPage (string) + * The SJS page to use. Use META_PAGE for the <meta> tag refresh + * case. Use HEADER_PAGE for the HTTP header case. + * @param delay (int) + * The amount, in ms, for the page to wait before attempting the + * refresh. + * + * @returns Promise + */ +function* testRealRefresh(refreshPage, delay) { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, function*(browser) { + yield pushPrefs(["accessibility.blockautorefresh", true]); + + browser.loadURI(refreshPage + "?p=" + TARGET_PAGE + "&d=" + delay); + yield BrowserTestUtils.browserLoaded(browser); + + // Once browserLoaded resolves, all nsIWebProgressListener callbacks + // should have fired, so the notification should be visible. + let notificationBox = gBrowser.getNotificationBox(browser); + let notification = notificationBox.currentNotification; + + ok(notification, "Notification should be visible"); + is(notification.value, "refresh-blocked", + "Should be showing the right notification"); + + // Then click the button to allow the refresh. + let buttons = notification.querySelectorAll(".notification-button"); + is(buttons.length, 1, "Should have one button."); + + // Prepare a Promise that should resolve when the refresh goes through + let refreshPromise = BrowserTestUtils.browserLoaded(browser); + buttons[0].click(); + + yield refreshPromise; + }); +} + +/** + * Tests the meta-tag case for both short and longer delay times. + */ +add_task(function* test_can_allow_refresh() { + yield testRealRefresh(META_PAGE, 0); + yield testRealRefresh(META_PAGE, 100); + yield testRealRefresh(META_PAGE, 500); +}); + +/** + * Tests that when a HTTP header case for both short and longer + * delay times. + */ +add_task(function* test_can_block_refresh_from_header() { + yield testRealRefresh(HEADER_PAGE, 0); + yield testRealRefresh(HEADER_PAGE, 100); + yield testRealRefresh(HEADER_PAGE, 500); +}); diff --git a/browser/base/content/test/general/browser_registerProtocolHandler_notification.html b/browser/base/content/test/general/browser_registerProtocolHandler_notification.html new file mode 100644 index 000000000..241b03b95 --- /dev/null +++ b/browser/base/content/test/general/browser_registerProtocolHandler_notification.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> +<html> + <head> + <title>Protocol registrar page</title> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> + </head> + <body> + <script type="text/javascript"> + navigator.registerProtocolHandler("testprotocol", + "https://example.com/foobar?uri=%s", + "Test Protocol"); + </script> + </body> +</html> diff --git a/browser/base/content/test/general/browser_registerProtocolHandler_notification.js b/browser/base/content/test/general/browser_registerProtocolHandler_notification.js new file mode 100644 index 000000000..b30ece0f6 --- /dev/null +++ b/browser/base/content/test/general/browser_registerProtocolHandler_notification.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + let notificationValue = "Protocol Registration: testprotocol"; + let testURI = "http://example.com/browser/" + + "browser/base/content/test/general/browser_registerProtocolHandler_notification.html"; + + waitForCondition(function() { + // Do not start until the notification is up + let notificationBox = window.gBrowser.getNotificationBox(); + let notification = notificationBox.getNotificationWithValue(notificationValue); + return notification; + }, + function() { + + let notificationBox = window.gBrowser.getNotificationBox(); + let notification = notificationBox.getNotificationWithValue(notificationValue); + ok(notification, "Notification box should be displayed"); + if (notification == null) { + finish(); + return; + } + is(notification.type, "info", "We expect this notification to have the type of 'info'."); + isnot(notification.image, null, "We expect this notification to have an icon."); + + let buttons = notification.getElementsByClassName("notification-button-default"); + is(buttons.length, 1, "We expect see one default button."); + + buttons = notification.getElementsByClassName("notification-button"); + is(buttons.length, 1, "We expect see one button."); + + let button = buttons[0]; + isnot(button.label, null, "We expect the add button to have a label."); + todo_isnot(button.accesskey, null, "We expect the add button to have a accesskey."); + + finish(); + }, "Still can not get notification after retry 100 times.", 100); + + window.gBrowser.selectedBrowser.loadURI(testURI); +} diff --git a/browser/base/content/test/general/browser_relatedTabs.js b/browser/base/content/test/general/browser_relatedTabs.js new file mode 100644 index 000000000..97cf51d84 --- /dev/null +++ b/browser/base/content/test/general/browser_relatedTabs.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function*() { + is(gBrowser.tabs.length, 1, "one tab is open initially"); + + // Add several new tabs in sequence, interrupted by selecting a + // different tab, moving a tab around and closing a tab, + // returning a list of opened tabs for verifying the expected order. + // The new tab behaviour is documented in bug 465673 + let tabs = []; + function addTab(aURL, aReferrer) { + let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer}); + tabs.push(tab); + return BrowserTestUtils.browserLoaded(tab.linkedBrowser); + } + + yield addTab("http://mochi.test:8888/#0"); + gBrowser.selectedTab = tabs[0]; + yield addTab("http://mochi.test:8888/#1"); + yield addTab("http://mochi.test:8888/#2", gBrowser.currentURI); + yield addTab("http://mochi.test:8888/#3", gBrowser.currentURI); + gBrowser.selectedTab = tabs[tabs.length - 1]; + gBrowser.selectedTab = tabs[0]; + yield addTab("http://mochi.test:8888/#4", gBrowser.currentURI); + gBrowser.selectedTab = tabs[3]; + yield addTab("http://mochi.test:8888/#5", gBrowser.currentURI); + gBrowser.removeTab(tabs.pop()); + yield addTab("about:blank", gBrowser.currentURI); + gBrowser.moveTabTo(gBrowser.selectedTab, 1); + yield addTab("http://mochi.test:8888/#6", gBrowser.currentURI); + yield addTab(); + yield addTab("http://mochi.test:8888/#7"); + + function testPosition(tabNum, expectedPosition, msg) { + is(Array.indexOf(gBrowser.tabs, tabs[tabNum]), expectedPosition, msg); + } + + testPosition(0, 3, "tab without referrer was opened to the far right"); + testPosition(1, 7, "tab without referrer was opened to the far right"); + testPosition(2, 5, "tab with referrer opened immediately to the right"); + testPosition(3, 1, "next tab with referrer opened further to the right"); + testPosition(4, 4, "tab selection changed, tab opens immediately to the right"); + testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was"); + testPosition(6, 2, "tab has moved, new tab opens immediately to the right"); + testPosition(7, 8, "blank tab without referrer opens at the end"); + testPosition(8, 9, "tab without referrer opens at the end"); + + tabs.forEach(gBrowser.removeTab, gBrowser); +}); diff --git a/browser/base/content/test/general/browser_remoteTroubleshoot.js b/browser/base/content/test/general/browser_remoteTroubleshoot.js new file mode 100644 index 000000000..5c939dbd0 --- /dev/null +++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {}); + +const TEST_URL_TAIL = "example.com/browser/browser/base/content/test/general/test_remoteTroubleshoot.html" +const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URL_TAIL, null, null); +const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URL_TAIL, null, null); +const TEST_URI_GOOD_OBJECT = Services.io.newURI("https://" + TEST_URL_TAIL + "?object", null, null); + +// Creates a one-shot web-channel for the test data to be sent back from the test page. +function promiseChannelResponse(channelID, originOrPermission) { + return new Promise((resolve, reject) => { + let channel = new WebChannel(channelID, originOrPermission); + channel.listen((id, data, target) => { + channel.stopListening(); + resolve(data); + }); + }); +} + +// Loads the specified URI in a new tab and waits for it to send us data on our +// test web-channel and resolves with that data. +function promiseNewChannelResponse(uri) { + let channelPromise = promiseChannelResponse("test-remote-troubleshooting-backchannel", + uri); + let tab = gBrowser.loadOneTab(uri.spec, { inBackground: false }); + return promiseTabLoaded(tab).then( + () => channelPromise + ).then(data => { + gBrowser.removeTab(tab); + return data; + }); +} + +add_task(function*() { + // We haven't set a permission yet - so even the "good" URI should fail. + let got = yield promiseNewChannelResponse(TEST_URI_GOOD); + // Should have no data. + Assert.ok(got.message === undefined, "should have failed to get any data"); + + // Add a permission manager entry for our URI. + Services.perms.add(TEST_URI_GOOD, + "remote-troubleshooting", + Services.perms.ALLOW_ACTION); + registerCleanupFunction(() => { + Services.perms.remove(TEST_URI_GOOD, "remote-troubleshooting"); + }); + + // Try again - now we are expecting a response with the actual data. + got = yield promiseNewChannelResponse(TEST_URI_GOOD); + + // Check some keys we expect to always get. + Assert.ok(got.message.extensions, "should have extensions"); + Assert.ok(got.message.graphics, "should have graphics"); + + // Check we have channel and build ID info: + Assert.equal(got.message.application.buildID, Services.appinfo.appBuildID, + "should have correct build ID"); + + let updateChannel = null; + try { + updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel; + } catch (ex) {} + if (!updateChannel) { + Assert.ok(!('updateChannel' in got.message.application), + "should not have update channel where not available."); + } else { + Assert.equal(got.message.application.updateChannel, updateChannel, + "should have correct update channel."); + } + + + // And check some keys we know we decline to return. + Assert.ok(!got.message.modifiedPreferences, "should not have a modifiedPreferences key"); + Assert.ok(!got.message.crashes, "should not have crash info"); + + // Now a http:// URI - should get nothing even with the permission setup. + got = yield promiseNewChannelResponse(TEST_URI_BAD); + Assert.ok(got.message === undefined, "should have failed to get any data"); + + // Check that the page can send an object as well if it's in the whitelist + let webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist"; + let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref); + let newWhitelist = origWhitelist + " https://example.com"; + Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist); + registerCleanupFunction(() => { + Services.prefs.clearUserPref(webchannelWhitelistPref); + }); + got = yield promiseNewChannelResponse(TEST_URI_GOOD_OBJECT); + Assert.ok(got.message, "should have gotten some data back"); +}); diff --git a/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js new file mode 100644 index 000000000..451323f50 --- /dev/null +++ b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Cu.import("resource://gre/modules/BrowserUtils.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); + +function makeInputStream(aString) { + let stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.data = aString; + return stream; // XPConnect will QI this to nsIInputStream for us. +} + +add_task(function* test_remoteWebNavigation_postdata() { + let obj = {}; + Cu.import("resource://testing-common/httpd.js", obj); + Cu.import("resource://services-common/utils.js", obj); + + let server = new obj.HttpServer(); + server.start(-1); + + let loadDeferred = Promise.defer(); + + server.registerPathHandler("/test", (request, response) => { + let body = obj.CommonUtils.readBytesFromInputStream(request.bodyInputStream); + is(body, "success", "request body is correct"); + is(request.method, "POST", "request was a post"); + response.write("Received from POST: " + body); + loadDeferred.resolve(); + }); + + let i = server.identity; + let path = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/test"; + + let postdata = + "Content-Length: 7\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "\r\n" + + "success"; + + openUILinkIn(path, "tab", null, makeInputStream(postdata)); + + yield loadDeferred.promise; + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + + let serverStoppedDeferred = Promise.defer(); + server.stop(function() { serverStoppedDeferred.resolve(); }); + yield serverStoppedDeferred.promise; +}); diff --git a/browser/base/content/test/general/browser_removeTabsToTheEnd.js b/browser/base/content/test/general/browser_removeTabsToTheEnd.js new file mode 100644 index 000000000..351085d74 --- /dev/null +++ b/browser/base/content/test/general/browser_removeTabsToTheEnd.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + // Add two new tabs after the original tab. Pin the first one. + let originalTab = gBrowser.selectedTab; + let newTab1 = gBrowser.addTab(); + gBrowser.addTab(); + gBrowser.pinTab(newTab1); + + // Check that there is only one closable tab from originalTab to the end + is(gBrowser.getTabsToTheEndFrom(originalTab).length, 1, + "One unpinned tab to the right"); + + // Remove tabs to the end + gBrowser.removeTabsToTheEndFrom(originalTab); + is(gBrowser.tabs.length, 2, "Length is 2"); + is(gBrowser.tabs[1], originalTab, "Starting tab is not removed"); + is(gBrowser.tabs[0], newTab1, "Pinned tab is not removed"); + + // Remove pinned tab + gBrowser.removeTab(newTab1); +} diff --git a/browser/base/content/test/general/browser_restore_isAppTab.js b/browser/base/content/test/general/browser_restore_isAppTab.js new file mode 100644 index 000000000..e20974d80 --- /dev/null +++ b/browser/base/content/test/general/browser_restore_isAppTab.js @@ -0,0 +1,160 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + +const DUMMY = "http://example.com/browser/browser/base/content/test/general/dummy_page.html"; + +function getMinidumpDirectory() { + let dir = Services.dirsvc.get('ProfD', Ci.nsIFile); + dir.append("minidumps"); + return dir; +} + +// This observer is needed so we can clean up all evidence of the crash so +// the testrunner thinks things are peachy. +var CrashObserver = { + observe: function(subject, topic, data) { + is(topic, 'ipc:content-shutdown', 'Received correct observer topic.'); + ok(subject instanceof Ci.nsIPropertyBag2, + 'Subject implements nsIPropertyBag2.'); + // we might see this called as the process terminates due to previous tests. + // We are only looking for "abnormal" exits... + if (!subject.hasKey("abnormal")) { + info("This is a normal termination and isn't the one we are looking for..."); + return; + } + + let dumpID; + if ('nsICrashReporter' in Ci) { + dumpID = subject.getPropertyAsAString('dumpID'); + ok(dumpID, "dumpID is present and not an empty string"); + } + + if (dumpID) { + let minidumpDirectory = getMinidumpDirectory(); + let file = minidumpDirectory.clone(); + file.append(dumpID + '.dmp'); + file.remove(true); + file = minidumpDirectory.clone(); + file.append(dumpID + '.extra'); + file.remove(true); + } + } +} +Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false); + +registerCleanupFunction(() => { + Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown'); +}); + +function frameScript() { + addMessageListener("Test:GetIsAppTab", function() { + sendAsyncMessage("Test:IsAppTab", { isAppTab: docShell.isAppTab }); + }); + + addMessageListener("Test:Crash", function() { + privateNoteIntentionalCrash(); + Components.utils.import("resource://gre/modules/ctypes.jsm"); + let zero = new ctypes.intptr_t(8); + let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + badptr.contents + }); +} + +function loadFrameScript(browser) { + browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true); +} + +function isBrowserAppTab(browser) { + return new Promise(resolve => { + function listener({ data }) { + browser.messageManager.removeMessageListener("Test:IsAppTab", listener); + resolve(data.isAppTab); + } + // It looks like same-process messages may be reordered by the message + // manager, so we need to wait one tick before sending the message. + executeSoon(function () { + browser.messageManager.addMessageListener("Test:IsAppTab", listener); + browser.messageManager.sendAsyncMessage("Test:GetIsAppTab"); + }); + }); +} + +// Restarts the child process by crashing it then reloading the tab +var restart = Task.async(function*(browser) { + // If the tab isn't remote this would crash the main process so skip it + if (!browser.isRemoteBrowser) + return; + + // Make sure the main process has all of the current tab state before crashing + yield TabStateFlusher.flush(browser); + + browser.messageManager.sendAsyncMessage("Test:Crash"); + yield promiseWaitForEvent(browser, "AboutTabCrashedLoad", false, true); + + let tab = gBrowser.getTabForBrowser(browser); + SessionStore.reviveCrashedTab(tab); + + yield promiseTabLoaded(tab); +}); + +add_task(function* navigate() { + let tab = gBrowser.addTab("about:robots"); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + yield waitForDocLoadComplete(); + loadFrameScript(browser); + let isAppTab = yield isBrowserAppTab(browser); + ok(!isAppTab, "Docshell shouldn't think it is an app tab"); + + gBrowser.pinTab(tab); + isAppTab = yield isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.loadURI(DUMMY); + yield waitForDocLoadComplete(); + loadFrameScript(browser); + isAppTab = yield isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.unpinTab(tab); + isAppTab = yield isBrowserAppTab(browser); + ok(!isAppTab, "Docshell shouldn't think it is an app tab"); + + gBrowser.pinTab(tab); + isAppTab = yield isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.loadURI("about:robots"); + yield waitForDocLoadComplete(); + loadFrameScript(browser); + isAppTab = yield isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.removeCurrentTab(); +}); + +add_task(function* crash() { + if (!gMultiProcessBrowser || !("nsICrashReporter" in Ci)) + return; + + let tab = gBrowser.addTab(DUMMY); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + yield waitForDocLoadComplete(); + loadFrameScript(browser); + let isAppTab = yield isBrowserAppTab(browser); + ok(!isAppTab, "Docshell shouldn't think it is an app tab"); + + gBrowser.pinTab(tab); + isAppTab = yield isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + yield restart(browser); + loadFrameScript(browser); + isAppTab = yield isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js new file mode 100644 index 000000000..4f4f5c398 --- /dev/null +++ b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js @@ -0,0 +1,39 @@ +// Bug 474792 - Clear "Never remember passwords for this site" when +// clearing site-specific settings in Clear Recent History dialog + +var tempScope = {}; +Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tempScope); +var Sanitizer = tempScope.Sanitizer; + +add_task(function*() { + var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + + // Add a disabled host + pwmgr.setLoginSavingEnabled("http://example.com", false); + // Sanity check + is(pwmgr.getLoginSavingEnabled("http://example.com"), false, + "example.com should be disabled for password saving since we haven't cleared that yet."); + + // Set up the sanitizer to just clear siteSettings + let s = new Sanitizer(); + s.ignoreTimespan = false; + s.prefDomain = "privacy.cpd."; + var itemPrefs = gPrefService.getBranch(s.prefDomain); + itemPrefs.setBoolPref("history", false); + itemPrefs.setBoolPref("downloads", false); + itemPrefs.setBoolPref("cache", false); + itemPrefs.setBoolPref("cookies", false); + itemPrefs.setBoolPref("formdata", false); + itemPrefs.setBoolPref("offlineApps", false); + itemPrefs.setBoolPref("passwords", false); + itemPrefs.setBoolPref("sessions", false); + itemPrefs.setBoolPref("siteSettings", true); + + // Clear it + yield s.sanitize(); + + // Make sure it's gone + is(pwmgr.getLoginSavingEnabled("http://example.com"), true, + "example.com should be enabled for password saving again now that we've cleared."); +}); diff --git a/browser/base/content/test/general/browser_sanitize-sitepermissions.js b/browser/base/content/test/general/browser_sanitize-sitepermissions.js new file mode 100644 index 000000000..1b43d62fc --- /dev/null +++ b/browser/base/content/test/general/browser_sanitize-sitepermissions.js @@ -0,0 +1,52 @@ +// Bug 380852 - Delete permission manager entries in Clear Recent History + +var tempScope = {}; +Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tempScope); +var Sanitizer = tempScope.Sanitizer; + +function countPermissions() { + let result = 0; + let enumerator = Services.perms.enumerator; + while (enumerator.hasMoreElements()) { + result++; + enumerator.getNext(); + } + return result; +} + +add_task(function* test() { + // sanitize before we start so we have a good baseline. + // Set up the sanitizer to just clear siteSettings + let s = new Sanitizer(); + s.ignoreTimespan = false; + s.prefDomain = "privacy.cpd."; + var itemPrefs = gPrefService.getBranch(s.prefDomain); + itemPrefs.setBoolPref("history", false); + itemPrefs.setBoolPref("downloads", false); + itemPrefs.setBoolPref("cache", false); + itemPrefs.setBoolPref("cookies", false); + itemPrefs.setBoolPref("formdata", false); + itemPrefs.setBoolPref("offlineApps", false); + itemPrefs.setBoolPref("passwords", false); + itemPrefs.setBoolPref("sessions", false); + itemPrefs.setBoolPref("siteSettings", true); + s.sanitize(); + + // Count how many permissions we start with - some are defaults that + // will not be sanitized. + let numAtStart = countPermissions(); + + // Add a permission entry + var pm = Services.perms; + pm.add(makeURI("http://example.com"), "testing", pm.ALLOW_ACTION); + + // Sanity check + ok(pm.enumerator.hasMoreElements(), "Permission manager should have elements, since we just added one"); + + // Clear it + yield s.sanitize(); + + // Make sure it's gone + is(numAtStart, countPermissions(), "Permission manager should have the same count it started with"); +}); diff --git a/browser/base/content/test/general/browser_sanitize-timespans.js b/browser/base/content/test/general/browser_sanitize-timespans.js new file mode 100644 index 000000000..3712c5e1c --- /dev/null +++ b/browser/base/content/test/general/browser_sanitize-timespans.js @@ -0,0 +1,733 @@ +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +requestLongerTimeout(2); + +// Bug 453440 - Test the timespan-based logic of the sanitizer code +var now_mSec = Date.now(); +var now_uSec = now_mSec * 1000; + +const kMsecPerMin = 60 * 1000; +const kUsecPerMin = 60 * 1000000; + +var tempScope = {}; +Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tempScope); +var Sanitizer = tempScope.Sanitizer; + +var FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory; +var Downloads = (Components.utils.import("resource://gre/modules/Downloads.jsm", {})).Downloads; + +function promiseFormHistoryRemoved() { + let deferred = Promise.defer(); + Services.obs.addObserver(function onfh() { + Services.obs.removeObserver(onfh, "satchel-storage-changed", false); + deferred.resolve(); + }, "satchel-storage-changed", false); + return deferred.promise; +} + +function promiseDownloadRemoved(list) { + let deferred = Promise.defer(); + + let view = { + onDownloadRemoved: function(download) { + list.removeView(view); + deferred.resolve(); + } + }; + + list.addView(view); + + return deferred.promise; +} + +add_task(function* test() { + yield setupDownloads(); + yield setupFormHistory(); + yield setupHistory(); + yield onHistoryReady(); +}); + +function countEntries(name, message, check) { + let deferred = Promise.defer(); + + var obj = {}; + if (name !== null) + obj.fieldname = name; + + let count; + FormHistory.count(obj, { handleResult: result => count = result, + handleError: function (error) { + deferred.reject(error) + throw new Error("Error occurred searching form history: " + error); + }, + handleCompletion: function (reason) { + if (!reason) { + check(count, message); + deferred.resolve(); + } + }, + }); + + return deferred.promise; +} + +function* onHistoryReady() { + var hoursSinceMidnight = new Date().getHours(); + var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes(); + + // Should test cookies here, but nsICookieManager/nsICookieService + // doesn't let us fake creation times. bug 463127 + + let s = new Sanitizer(); + s.ignoreTimespan = false; + s.prefDomain = "privacy.cpd."; + var itemPrefs = gPrefService.getBranch(s.prefDomain); + itemPrefs.setBoolPref("history", true); + itemPrefs.setBoolPref("downloads", true); + itemPrefs.setBoolPref("cache", false); + itemPrefs.setBoolPref("cookies", false); + itemPrefs.setBoolPref("formdata", true); + itemPrefs.setBoolPref("offlineApps", false); + itemPrefs.setBoolPref("passwords", false); + itemPrefs.setBoolPref("sessions", false); + itemPrefs.setBoolPref("siteSettings", false); + + let publicList = yield Downloads.getList(Downloads.PUBLIC); + let downloadPromise = promiseDownloadRemoved(publicList); + let formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 10 minutes ago + s.range = [now_uSec - 10*60*1000000, now_uSec]; + yield s.sanitize(); + s.range = null; + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))), + "Pretend visit to 10minutes.com should now be deleted"); + ok((yield promiseIsURIVisited(makeURI("http://1hour.com"))), + "Pretend visit to 1hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))), + "Pretend visit to 1hour10minutes.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))), + "Pretend visit to 2hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))), + "Pretend visit to 2hour10minutes.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))), + "Pretend visit to 4hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should should still exist"); + if (minutesSinceMidnight > 10) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + let checkZero = function(num, message) { is(num, 0, message); } + let checkOne = function(num, message) { is(num, 1, message); } + + yield countEntries("10minutes", "10minutes form entry should be deleted", checkZero); + yield countEntries("1hour", "1hour form entry should still exist", checkOne); + yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne); + yield countEntries("2hour", "2hour form entry should still exist", checkOne); + yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne); + yield countEntries("4hour", "4hour form entry should still exist", checkOne); + yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne); + if (minutesSinceMidnight > 10) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-10-minutes")), "10 minute download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should still be present"); + ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present"); + + if (minutesSinceMidnight > 10) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 1 hour + Sanitizer.prefs.setIntPref("timeSpan", 1); + yield s.sanitize(); + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))), + "Pretend visit to 1hour.com should now be deleted"); + ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))), + "Pretend visit to 1hour10minutes.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))), + "Pretend visit to 2hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))), + "Pretend visit to 2hour10minutes.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))), + "Pretend visit to 4hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should should still exist"); + if (hoursSinceMidnight > 1) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + yield countEntries("1hour", "1hour form entry should be deleted", checkZero); + yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne); + yield countEntries("2hour", "2hour form entry should still exist", checkOne); + yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne); + yield countEntries("4hour", "4hour form entry should still exist", checkOne); + yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne); + if (hoursSinceMidnight > 1) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present"); + + if (hoursSinceMidnight > 1) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 1 hour 10 minutes + s.range = [now_uSec - 70*60*1000000, now_uSec]; + yield s.sanitize(); + s.range = null; + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))), + "Pretend visit to 1hour10minutes.com should now be deleted"); + ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))), + "Pretend visit to 2hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))), + "Pretend visit to 2hour10minutes.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))), + "Pretend visit to 4hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should should still exist"); + if (minutesSinceMidnight > 70) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + yield countEntries("1hour10minutes", "1hour10minutes form entry should be deleted", checkZero); + yield countEntries("2hour", "2hour form entry should still exist", checkOne); + yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne); + yield countEntries("4hour", "4hour form entry should still exist", checkOne); + yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne); + if (minutesSinceMidnight > 70) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute old download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present"); + if (minutesSinceMidnight > 70) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 2 hours + Sanitizer.prefs.setIntPref("timeSpan", 2); + yield s.sanitize(); + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))), + "Pretend visit to 2hour.com should now be deleted"); + ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))), + "Pretend visit to 2hour10minutes.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))), + "Pretend visit to 4hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should should still exist"); + if (hoursSinceMidnight > 2) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + yield countEntries("2hour", "2hour form entry should be deleted", checkZero); + yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne); + yield countEntries("4hour", "4hour form entry should still exist", checkOne); + yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne); + if (hoursSinceMidnight > 2) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present"); + if (hoursSinceMidnight > 2) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 2 hours 10 minutes + s.range = [now_uSec - 130*60*1000000, now_uSec]; + yield s.sanitize(); + s.range = null; + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))), + "Pretend visit to 2hour10minutes.com should now be deleted"); + ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))), + "Pretend visit to 4hour.com should should still exist"); + ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should should still exist"); + if (minutesSinceMidnight > 130) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + yield countEntries("2hour10minutes", "2hour10minutes form entry should be deleted", checkZero); + yield countEntries("4hour", "4hour form entry should still exist", checkOne); + yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne); + if (minutesSinceMidnight > 130) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute old download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + if (minutesSinceMidnight > 130) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 4 hours + Sanitizer.prefs.setIntPref("timeSpan", 3); + yield s.sanitize(); + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))), + "Pretend visit to 4hour.com should now be deleted"); + ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should should still exist"); + if (hoursSinceMidnight > 4) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + yield countEntries("4hour", "4hour form entry should be deleted", checkZero); + yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne); + if (hoursSinceMidnight > 4) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + if (hoursSinceMidnight > 4) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear 4 hours 10 minutes + s.range = [now_uSec - 250*60*1000000, now_uSec]; + yield s.sanitize(); + s.range = null; + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))), + "Pretend visit to 4hour10minutes.com should now be deleted"); + if (minutesSinceMidnight > 250) { + ok((yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should still exist"); + } + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + + yield countEntries("4hour10minutes", "4hour10minutes form entry should be deleted", checkZero); + if (minutesSinceMidnight > 250) + yield countEntries("today", "today form entry should still exist", checkOne); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + + ok(!(yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should now be deleted"); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + if (minutesSinceMidnight > 250) + ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present"); + + // The 'Today' download might have been already deleted, in which case we + // should not wait for a download removal notification. + if (minutesSinceMidnight > 250) { + downloadPromise = promiseDownloadRemoved(publicList); + } else { + downloadPromise = Promise.resolve(); + } + formHistoryPromise = promiseFormHistoryRemoved(); + + // Clear Today + Sanitizer.prefs.setIntPref("timeSpan", 4); + yield s.sanitize(); + + yield formHistoryPromise; + yield downloadPromise; + + // Be careful. If we add our objectss just before midnight, and sanitize + // runs immediately after, they won't be expired. This is expected, but + // we should not test in that case. We cannot just test for opposite + // condition because we could cross midnight just one moment after we + // cache our time, then we would have an even worse random failure. + var today = isToday(new Date(now_mSec)); + if (today) { + ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))), + "Pretend visit to today.com should now be deleted"); + + yield countEntries("today", "today form entry should be deleted", checkZero); + ok(!(yield downloadExists(publicList, "fakefile-today")), "'Today' download should now be deleted"); + } + + ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should still exist"); + yield countEntries("b4today", "b4today form entry should still exist", checkOne); + ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present"); + + downloadPromise = promiseDownloadRemoved(publicList); + formHistoryPromise = promiseFormHistoryRemoved(); + + // Choose everything + Sanitizer.prefs.setIntPref("timeSpan", 0); + yield s.sanitize(); + + yield formHistoryPromise; + yield downloadPromise; + + ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))), + "Pretend visit to before-today.com should now be deleted"); + + yield countEntries("b4today", "b4today form entry should be deleted", checkZero); + + ok(!(yield downloadExists(publicList, "fakefile-old")), "Year old download should now be deleted"); +} + +function setupHistory() { + let deferred = Promise.defer(); + + let places = []; + + function addPlace(aURI, aTitle, aVisitDate) { + places.push({ + uri: aURI, + title: aTitle, + visits: [{ + visitDate: aVisitDate, + transitionType: Ci.nsINavHistoryService.TRANSITION_LINK + }] + }); + } + + addPlace(makeURI("http://10minutes.com/"), "10 minutes ago", now_uSec - 10 * kUsecPerMin); + addPlace(makeURI("http://1hour.com/"), "Less than 1 hour ago", now_uSec - 45 * kUsecPerMin); + addPlace(makeURI("http://1hour10minutes.com/"), "1 hour 10 minutes ago", now_uSec - 70 * kUsecPerMin); + addPlace(makeURI("http://2hour.com/"), "Less than 2 hours ago", now_uSec - 90 * kUsecPerMin); + addPlace(makeURI("http://2hour10minutes.com/"), "2 hours 10 minutes ago", now_uSec - 130 * kUsecPerMin); + addPlace(makeURI("http://4hour.com/"), "Less than 4 hours ago", now_uSec - 180 * kUsecPerMin); + addPlace(makeURI("http://4hour10minutes.com/"), "4 hours 10 minutesago", now_uSec - 250 * kUsecPerMin); + + let today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(1); + addPlace(makeURI("http://today.com/"), "Today", today.getTime() * 1000); + + let lastYear = new Date(); + lastYear.setFullYear(lastYear.getFullYear() - 1); + addPlace(makeURI("http://before-today.com/"), "Before Today", lastYear.getTime() * 1000); + PlacesUtils.asyncHistory.updatePlaces(places, { + handleError: () => ok(false, "Unexpected error in adding visit."), + handleResult: () => { }, + handleCompletion: () => deferred.resolve() + }); + + return deferred.promise; +} + +function* setupFormHistory() { + + function searchEntries(terms, params) { + let deferred = Promise.defer(); + + let results = []; + FormHistory.search(terms, params, { handleResult: result => results.push(result), + handleError: function (error) { + deferred.reject(error); + throw new Error("Error occurred searching form history: " + error); + }, + handleCompletion: function (reason) { deferred.resolve(results); } + }); + return deferred.promise; + } + + function update(changes) + { + let deferred = Promise.defer(); + FormHistory.update(changes, { handleError: function (error) { + deferred.reject(error); + throw new Error("Error occurred searching form history: " + error); + }, + handleCompletion: function (reason) { deferred.resolve(); } + }); + return deferred.promise; + } + + // Make sure we've got a clean DB to start with, then add the entries we'll be testing. + yield update( + [{ + op: "remove" + }, + { + op : "add", + fieldname : "10minutes", + value : "10m" + }, { + op : "add", + fieldname : "1hour", + value : "1h" + }, { + op : "add", + fieldname : "1hour10minutes", + value : "1h10m" + }, { + op : "add", + fieldname : "2hour", + value : "2h" + }, { + op : "add", + fieldname : "2hour10minutes", + value : "2h10m" + }, { + op : "add", + fieldname : "4hour", + value : "4h" + }, { + op : "add", + fieldname : "4hour10minutes", + value : "4h10m" + }, { + op : "add", + fieldname : "today", + value : "1d" + }, { + op : "add", + fieldname : "b4today", + value : "1y" + }]); + + // Artifically age the entries to the proper vintage. + let timestamp = now_uSec - 10 * kUsecPerMin; + let results = yield searchEntries(["guid"], { fieldname: "10minutes" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + timestamp = now_uSec - 45 * kUsecPerMin; + results = yield searchEntries(["guid"], { fieldname: "1hour" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + timestamp = now_uSec - 70 * kUsecPerMin; + results = yield searchEntries(["guid"], { fieldname: "1hour10minutes" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + timestamp = now_uSec - 90 * kUsecPerMin; + results = yield searchEntries(["guid"], { fieldname: "2hour" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + timestamp = now_uSec - 130 * kUsecPerMin; + results = yield searchEntries(["guid"], { fieldname: "2hour10minutes" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + timestamp = now_uSec - 180 * kUsecPerMin; + results = yield searchEntries(["guid"], { fieldname: "4hour" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + timestamp = now_uSec - 250 * kUsecPerMin; + results = yield searchEntries(["guid"], { fieldname: "4hour10minutes" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + let today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(1); + timestamp = today.getTime() * 1000; + results = yield searchEntries(["guid"], { fieldname: "today" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + let lastYear = new Date(); + lastYear.setFullYear(lastYear.getFullYear() - 1); + timestamp = lastYear.getTime() * 1000; + results = yield searchEntries(["guid"], { fieldname: "b4today" }); + yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid }); + + var checks = 0; + let checkOne = function(num, message) { is(num, 1, message); checks++; } + + // Sanity check. + yield countEntries("10minutes", "Checking for 10minutes form history entry creation", checkOne); + yield countEntries("1hour", "Checking for 1hour form history entry creation", checkOne); + yield countEntries("1hour10minutes", "Checking for 1hour10minutes form history entry creation", checkOne); + yield countEntries("2hour", "Checking for 2hour form history entry creation", checkOne); + yield countEntries("2hour10minutes", "Checking for 2hour10minutes form history entry creation", checkOne); + yield countEntries("4hour", "Checking for 4hour form history entry creation", checkOne); + yield countEntries("4hour10minutes", "Checking for 4hour10minutes form history entry creation", checkOne); + yield countEntries("today", "Checking for today form history entry creation", checkOne); + yield countEntries("b4today", "Checking for b4today form history entry creation", checkOne); + is(checks, 9, "9 checks made"); +} + +function* setupDownloads() { + + let publicList = yield Downloads.getList(Downloads.PUBLIC); + + let download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", + target: "fakefile-10-minutes" + }); + download.startTime = new Date(now_mSec - 10 * kMsecPerMin), // 10 minutes ago + download.canceled = true; + yield publicList.add(download); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440", + target: "fakefile-1-hour" + }); + download.startTime = new Date(now_mSec - 45 * kMsecPerMin), // 45 minutes ago + download.canceled = true; + yield publicList.add(download); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", + target: "fakefile-1-hour-10-minutes" + }); + download.startTime = new Date(now_mSec - 70 * kMsecPerMin), // 70 minutes ago + download.canceled = true; + yield publicList.add(download); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440", + target: "fakefile-2-hour" + }); + download.startTime = new Date(now_mSec - 90 * kMsecPerMin), // 90 minutes ago + download.canceled = true; + yield publicList.add(download); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", + target: "fakefile-2-hour-10-minutes" + }); + download.startTime = new Date(now_mSec - 130 * kMsecPerMin), // 130 minutes ago + download.canceled = true; + yield publicList.add(download); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440", + target: "fakefile-4-hour" + }); + download.startTime = new Date(now_mSec - 180 * kMsecPerMin), // 180 minutes ago + download.canceled = true; + yield publicList.add(download); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", + target: "fakefile-4-hour-10-minutes" + }); + download.startTime = new Date(now_mSec - 250 * kMsecPerMin), // 250 minutes ago + download.canceled = true; + yield publicList.add(download); + + // Add "today" download + let today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(1); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440", + target: "fakefile-today" + }); + download.startTime = today, // 12:00:01 AM this morning + download.canceled = true; + yield publicList.add(download); + + // Add "before today" download + let lastYear = new Date(); + lastYear.setFullYear(lastYear.getFullYear() - 1); + + download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440", + target: "fakefile-old" + }); + download.startTime = lastYear, + download.canceled = true; + yield publicList.add(download); + + // Confirm everything worked + let downloads = yield publicList.getAll(); + is(downloads.length, 9, "9 Pretend downloads added"); + + ok((yield downloadExists(publicList, "fakefile-old")), "Pretend download for everything case should exist"); + ok((yield downloadExists(publicList, "fakefile-10-minutes")), "Pretend download for 10-minutes case should exist"); + ok((yield downloadExists(publicList, "fakefile-1-hour")), "Pretend download for 1-hour case should exist"); + ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "Pretend download for 1-hour-10-minutes case should exist"); + ok((yield downloadExists(publicList, "fakefile-2-hour")), "Pretend download for 2-hour case should exist"); + ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "Pretend download for 2-hour-10-minutes case should exist"); + ok((yield downloadExists(publicList, "fakefile-4-hour")), "Pretend download for 4-hour case should exist"); + ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "Pretend download for 4-hour-10-minutes case should exist"); + ok((yield downloadExists(publicList, "fakefile-today")), "Pretend download for Today case should exist"); +} + +/** + * Checks to see if the downloads with the specified id exists. + * + * @param aID + * The ids of the downloads to check. + */ +let downloadExists = Task.async(function* (list, path) { + let listArray = yield list.getAll(); + return listArray.some(i => i.target.path == path); +}); + +function isToday(aDate) { + return aDate.getDate() == new Date().getDate(); +} diff --git a/browser/base/content/test/general/browser_sanitizeDialog.js b/browser/base/content/test/general/browser_sanitizeDialog.js new file mode 100644 index 000000000..50546be45 --- /dev/null +++ b/browser/base/content/test/general/browser_sanitizeDialog.js @@ -0,0 +1,1027 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests the sanitize dialog (a.k.a. the clear recent history dialog). + * See bug 480169. + * + * The purpose of this test is not to fully flex the sanitize timespan code; + * browser/base/content/test/general/browser_sanitize-timespans.js does that. This + * test checks the UI of the dialog and makes sure it's correctly connected to + * the sanitize timespan code. + * + * Some of this code, especially the history creation parts, was taken from + * browser/base/content/test/general/browser_sanitize-timespans.js. + */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {}); + +XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", + "resource://gre/modules/FormHistory.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Timer", + "resource://gre/modules/Timer.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm"); + +var tempScope = {}; +Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", tempScope); +var Sanitizer = tempScope.Sanitizer; + +const kMsecPerMin = 60 * 1000; +const kUsecPerMin = 60 * 1000000; + +add_task(function* init() { + requestLongerTimeout(3); + yield blankSlate(); + registerCleanupFunction(function* () { + yield blankSlate(); + yield PlacesTestUtils.promiseAsyncUpdates(); + }); +}); + +/** + * Initializes the dialog to its default state. + */ +add_task(function* default_state() { + let wh = new WindowHelper(); + wh.onload = function () { + // Select "Last Hour" + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + // Hide details + if (!this.getItemList().collapsed) + this.toggleDetails(); + this.acceptDialog(); + }; + wh.open(); + yield wh.promiseClosed; +}); + +/** + * Cancels the dialog, makes sure history not cleared. + */ +add_task(function* test_cancel() { + // Add history (within the past hour) + let uris = []; + let places = []; + let pURI; + for (let i = 0; i < 30; i++) { + pURI = makeURI("http://" + i + "-minutes-ago.com/"); + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)}); + uris.push(pURI); + } + yield PlacesTestUtils.addVisits(places); + + let wh = new WindowHelper(); + wh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + this.checkPrefCheckbox("history", false); + this.checkDetails(false); + + // Show details + this.toggleDetails(); + this.checkDetails(true); + + // Hide details + this.toggleDetails(); + this.checkDetails(false); + this.cancelDialog(); + }; + wh.onunload = function* () { + yield promiseHistoryClearedState(uris, false); + yield blankSlate(); + yield promiseHistoryClearedState(uris, true); + }; + wh.open(); + yield wh.promiseClosed; +}); + +/** + * Ensures that the combined history-downloads checkbox clears both history + * visits and downloads when checked; the dialog respects simple timespan. + */ +add_task(function* test_history_downloads_checked() { + // Add downloads (within the past hour). + let downloadIDs = []; + for (let i = 0; i < 5; i++) { + yield addDownloadWithMinutesAgo(downloadIDs, i); + } + // Add downloads (over an hour ago). + let olderDownloadIDs = []; + for (let i = 0; i < 5; i++) { + yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i); + } + + // Add history (within the past hour). + let uris = []; + let places = []; + let pURI; + for (let i = 0; i < 30; i++) { + pURI = makeURI("http://" + i + "-minutes-ago.com/"); + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)}); + uris.push(pURI); + } + // Add history (over an hour ago). + let olderURIs = []; + for (let i = 0; i < 5; i++) { + pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/"); + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)}); + olderURIs.push(pURI); + } + let promiseSanitized = promiseSanitizationComplete(); + + yield PlacesTestUtils.addVisits(places); + + let wh = new WindowHelper(); + wh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + this.checkPrefCheckbox("history", true); + this.acceptDialog(); + }; + wh.onunload = function* () { + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR, + "timeSpan pref should be hour after accepting dialog with " + + "hour selected"); + boolPrefIs("cpd.history", true, + "history pref should be true after accepting dialog with " + + "history checkbox checked"); + boolPrefIs("cpd.downloads", true, + "downloads pref should be true after accepting dialog with " + + "history checkbox checked"); + + yield promiseSanitized; + + // History visits and downloads within one hour should be cleared. + yield promiseHistoryClearedState(uris, true); + yield ensureDownloadsClearedState(downloadIDs, true); + + // Visits and downloads > 1 hour should still exist. + yield promiseHistoryClearedState(olderURIs, false); + yield ensureDownloadsClearedState(olderDownloadIDs, false); + + // OK, done, cleanup after ourselves. + yield blankSlate(); + yield promiseHistoryClearedState(olderURIs, true); + yield ensureDownloadsClearedState(olderDownloadIDs, true); + }; + wh.open(); + yield wh.promiseClosed; +}); + +/** + * Ensures that the combined history-downloads checkbox removes neither + * history visits nor downloads when not checked. + */ +add_task(function* test_history_downloads_unchecked() { + // Add form entries + let formEntries = []; + + for (let i = 0; i < 5; i++) { + formEntries.push((yield promiseAddFormEntryWithMinutesAgo(i))); + } + + + // Add downloads (within the past hour). + let downloadIDs = []; + for (let i = 0; i < 5; i++) { + yield addDownloadWithMinutesAgo(downloadIDs, i); + } + + // Add history, downloads, form entries (within the past hour). + let uris = []; + let places = []; + let pURI; + for (let i = 0; i < 5; i++) { + pURI = makeURI("http://" + i + "-minutes-ago.com/"); + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)}); + uris.push(pURI); + } + + yield PlacesTestUtils.addVisits(places); + let wh = new WindowHelper(); + wh.onload = function () { + is(this.isWarningPanelVisible(), false, + "Warning panel should be hidden after previously accepting dialog " + + "with a predefined timespan"); + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + + // Remove only form entries, leave history (including downloads). + this.checkPrefCheckbox("history", false); + this.checkPrefCheckbox("formdata", true); + this.acceptDialog(); + }; + wh.onunload = function* () { + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR, + "timeSpan pref should be hour after accepting dialog with " + + "hour selected"); + boolPrefIs("cpd.history", false, + "history pref should be false after accepting dialog with " + + "history checkbox unchecked"); + boolPrefIs("cpd.downloads", false, + "downloads pref should be false after accepting dialog with " + + "history checkbox unchecked"); + + // Of the three only form entries should be cleared. + yield promiseHistoryClearedState(uris, false); + yield ensureDownloadsClearedState(downloadIDs, false); + + for (let entry of formEntries) { + let exists = yield formNameExists(entry); + is(exists, false, "form entry " + entry + " should no longer exist"); + } + + // OK, done, cleanup after ourselves. + yield blankSlate(); + yield promiseHistoryClearedState(uris, true); + yield ensureDownloadsClearedState(downloadIDs, true); + }; + wh.open(); + yield wh.promiseClosed; +}); + +/** + * Ensures that the "Everything" duration option works. + */ +add_task(function* test_everything() { + // Add history. + let uris = []; + let places = []; + let pURI; + // within past hour, within past two hours, within past four hours and + // outside past four hours + [10, 70, 130, 250].forEach(function(aValue) { + pURI = makeURI("http://" + aValue + "-minutes-ago.com/"); + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)}); + uris.push(pURI); + }); + + let promiseSanitized = promiseSanitizationComplete(); + + yield PlacesTestUtils.addVisits(places); + let wh = new WindowHelper(); + wh.onload = function () { + is(this.isWarningPanelVisible(), false, + "Warning panel should be hidden after previously accepting dialog " + + "with a predefined timespan"); + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + this.checkPrefCheckbox("history", true); + this.checkDetails(true); + + // Hide details + this.toggleDetails(); + this.checkDetails(false); + + // Show details + this.toggleDetails(); + this.checkDetails(true); + + this.acceptDialog(); + }; + wh.onunload = function* () { + yield promiseSanitized; + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING, + "timeSpan pref should be everything after accepting dialog " + + "with everything selected"); + + yield promiseHistoryClearedState(uris, true); + }; + wh.open(); + yield wh.promiseClosed; +}); + +/** + * Ensures that the "Everything" warning is visible on dialog open after + * the previous test. + */ +add_task(function* test_everything_warning() { + // Add history. + let uris = []; + let places = []; + let pURI; + // within past hour, within past two hours, within past four hours and + // outside past four hours + [10, 70, 130, 250].forEach(function(aValue) { + pURI = makeURI("http://" + aValue + "-minutes-ago.com/"); + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)}); + uris.push(pURI); + }); + + let promiseSanitized = promiseSanitizationComplete(); + + yield PlacesTestUtils.addVisits(places); + let wh = new WindowHelper(); + wh.onload = function () { + is(this.isWarningPanelVisible(), true, + "Warning panel should be visible after previously accepting dialog " + + "with clearing everything"); + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + this.checkPrefCheckbox("history", true); + this.acceptDialog(); + }; + wh.onunload = function* () { + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING, + "timeSpan pref should be everything after accepting dialog " + + "with everything selected"); + + yield promiseSanitized; + + yield promiseHistoryClearedState(uris, true); + }; + wh.open(); + yield wh.promiseClosed; +}); + +/** + * The next three tests checks that when a certain history item cannot be + * cleared then the checkbox should be both disabled and unchecked. + * In addition, we ensure that this behavior does not modify the preferences. + */ +add_task(function* test_cannot_clear_history() { + // Add form entries + let formEntries = [ (yield promiseAddFormEntryWithMinutesAgo(10)) ]; + + let promiseSanitized = promiseSanitizationComplete(); + + // Add history. + let pURI = makeURI("http://" + 10 + "-minutes-ago.com/"); + yield PlacesTestUtils.addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)}); + let uris = [ pURI ]; + + let wh = new WindowHelper(); + wh.onload = function() { + // Check that the relevant checkboxes are enabled + var cb = this.win.document.querySelectorAll( + "#itemList > [preference='privacy.cpd.formdata']"); + ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " + + "clear formdata should be enabled."); + + cb = this.win.document.querySelectorAll( + "#itemList > [preference='privacy.cpd.history']"); + ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " + + "clear history should be enabled."); + + this.checkAllCheckboxes(); + this.acceptDialog(); + }; + wh.onunload = function* () { + yield promiseSanitized; + + yield promiseHistoryClearedState(uris, true); + + let exists = yield formNameExists(formEntries[0]); + is(exists, false, "form entry " + formEntries[0] + " should no longer exist"); + }; + wh.open(); + yield wh.promiseClosed; +}); + +add_task(function* test_no_formdata_history_to_clear() { + let promiseSanitized = promiseSanitizationComplete(); + let wh = new WindowHelper(); + wh.onload = function() { + boolPrefIs("cpd.history", true, + "history pref should be true after accepting dialog with " + + "history checkbox checked"); + boolPrefIs("cpd.formdata", true, + "formdata pref should be true after accepting dialog with " + + "formdata checkbox checked"); + + var cb = this.win.document.querySelectorAll( + "#itemList > [preference='privacy.cpd.history']"); + ok(cb.length == 1 && !cb[0].disabled && cb[0].checked, + "There is no history, but history checkbox should always be enabled " + + "and will be checked from previous preference."); + + this.acceptDialog(); + } + wh.open(); + yield wh.promiseClosed; + yield promiseSanitized; +}); + +add_task(function* test_form_entries() { + let formEntry = (yield promiseAddFormEntryWithMinutesAgo(10)); + + let promiseSanitized = promiseSanitizationComplete(); + + let wh = new WindowHelper(); + wh.onload = function() { + boolPrefIs("cpd.formdata", true, + "formdata pref should persist previous value after accepting " + + "dialog where you could not clear formdata."); + + var cb = this.win.document.querySelectorAll( + "#itemList > [preference='privacy.cpd.formdata']"); + + info("There exists formEntries so the checkbox should be in sync with the pref."); + is(cb.length, 1, "There is only one checkbox for form data"); + ok(!cb[0].disabled, "The checkbox is enabled"); + ok(cb[0].checked, "The checkbox is checked"); + + this.acceptDialog(); + }; + wh.onunload = function* () { + yield promiseSanitized; + let exists = yield formNameExists(formEntry); + is(exists, false, "form entry " + formEntry + " should no longer exist"); + }; + wh.open(); + yield wh.promiseClosed; +}); + + +/** + * Ensure that toggling details persists + * across dialog openings. + */ +add_task(function* test_toggling_details_persists() { + { + let wh = new WindowHelper(); + wh.onload = function () { + // Check all items and select "Everything" + this.checkAllCheckboxes(); + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + + // Hide details + this.toggleDetails(); + this.checkDetails(false); + this.acceptDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } + { + let wh = new WindowHelper(); + wh.onload = function () { + // Details should remain closed because all items are checked. + this.checkDetails(false); + + // Uncheck history. + this.checkPrefCheckbox("history", false); + this.acceptDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } + { + let wh = new WindowHelper(); + wh.onload = function () { + // Details should be open because not all items are checked. + this.checkDetails(true); + + // Modify the Site Preferences item state (bug 527820) + this.checkAllCheckboxes(); + this.checkPrefCheckbox("siteSettings", false); + this.acceptDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } + { + let wh = new WindowHelper(); + wh.onload = function () { + // Details should be open because not all items are checked. + this.checkDetails(true); + + // Hide details + this.toggleDetails(); + this.checkDetails(false); + this.cancelDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } + { + let wh = new WindowHelper(); + wh.onload = function () { + // Details should be open because not all items are checked. + this.checkDetails(true); + + // Select another duration + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + // Hide details + this.toggleDetails(); + this.checkDetails(false); + this.acceptDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } + { + let wh = new WindowHelper(); + wh.onload = function () { + // Details should not be open because "Last Hour" is selected + this.checkDetails(false); + + this.cancelDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } + { + let wh = new WindowHelper(); + wh.onload = function () { + // Details should have remained closed + this.checkDetails(false); + + // Show details + this.toggleDetails(); + this.checkDetails(true); + this.cancelDialog(); + }; + wh.open(); + yield wh.promiseClosed; + } +}); + +// Test for offline cache deletion +add_task(function* test_offline_cache() { + // Prepare stuff, we will work with www.example.com + var URL = "http://www.example.com"; + var URI = makeURI(URL); + var principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI); + + // Give www.example.com privileges to store offline data + Services.perms.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION); + Services.perms.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN); + + // Store something to the offline cache + var appcacheserv = Cc["@mozilla.org/network/application-cache-service;1"] + .getService(Ci.nsIApplicationCacheService); + var appcachegroupid = appcacheserv.buildGroupIDForInfo(makeURI(URL + "/manifest"), LoadContextInfo.default); + var appcache = appcacheserv.createApplicationCache(appcachegroupid); + var storage = Services.cache2.appCacheStorage(LoadContextInfo.default, appcache); + + // Open the dialog + let wh = new WindowHelper(); + wh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + // Show details + this.toggleDetails(); + // Clear only offlineApps + this.uncheckAllCheckboxes(); + this.checkPrefCheckbox("offlineApps", true); + this.acceptDialog(); + }; + wh.onunload = function () { + // Check if the cache has been deleted + var size = -1; + var visitor = { + onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory) + { + size = aConsumption; + } + }; + storage.asyncVisitStorage(visitor, false); + // Offline cache visit happens synchronously, since it's forwarded to the old code + is(size, 0, "offline application cache entries evicted"); + }; + + var cacheListener = { + onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; }, + onCacheEntryAvailable: function (entry, isnew, unused, status) { + is(status, Cr.NS_OK); + var stream = entry.openOutputStream(0); + var content = "content"; + stream.write(content, content.length); + stream.close(); + entry.close(); + wh.open(); + } + }; + + storage.asyncOpenURI(makeURI(URL), "", Ci.nsICacheStorage.OPEN_TRUNCATE, cacheListener); + yield wh.promiseClosed; +}); + +// Test for offline apps permission deletion +add_task(function* test_offline_apps_permissions() { + // Prepare stuff, we will work with www.example.com + var URL = "http://www.example.com"; + var URI = makeURI(URL); + var principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {}); + + let promiseSanitized = promiseSanitizationComplete(); + + // Open the dialog + let wh = new WindowHelper(); + wh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + // Show details + this.toggleDetails(); + // Clear only offlineApps + this.uncheckAllCheckboxes(); + this.checkPrefCheckbox("siteSettings", true); + this.acceptDialog(); + }; + wh.onunload = function* () { + yield promiseSanitized; + + // Check all has been deleted (privileges, data, cache) + is(Services.perms.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed"); + }; + wh.open(); + yield wh.promiseClosed; +}); + +var now_mSec = Date.now(); +var now_uSec = now_mSec * 1000; + +/** + * This wraps the dialog and provides some convenience methods for interacting + * with it. + * + * @param aWin + * The dialog's nsIDOMWindow + */ +function WindowHelper(aWin) { + this.win = aWin; + this.promiseClosed = new Promise(resolve => { this._resolveClosed = resolve }); +} + +WindowHelper.prototype = { + /** + * "Presses" the dialog's OK button. + */ + acceptDialog: function () { + is(this.win.document.documentElement.getButton("accept").disabled, false, + "Dialog's OK button should not be disabled"); + this.win.document.documentElement.acceptDialog(); + }, + + /** + * "Presses" the dialog's Cancel button. + */ + cancelDialog: function () { + this.win.document.documentElement.cancelDialog(); + }, + + /** + * Ensures that the details progressive disclosure button and the item list + * hidden by it match up. Also makes sure the height of the dialog is + * sufficient for the item list and warning panel. + * + * @param aShouldBeShown + * True if you expect the details to be shown and false if hidden + */ + checkDetails: function (aShouldBeShown) { + let button = this.getDetailsButton(); + let list = this.getItemList(); + let hidden = list.hidden || list.collapsed; + is(hidden, !aShouldBeShown, + "Details should be " + (aShouldBeShown ? "shown" : "hidden") + + " but were actually " + (hidden ? "hidden" : "shown")); + let dir = hidden ? "down" : "up"; + is(button.className, "expander-" + dir, + "Details button should be " + dir + " because item list is " + + (hidden ? "" : "not ") + "hidden"); + let height = 0; + if (!hidden) { + ok(list.boxObject.height > 30, "listbox has sufficient size") + height += list.boxObject.height; + } + if (this.isWarningPanelVisible()) + height += this.getWarningPanel().boxObject.height; + ok(height < this.win.innerHeight, + "Window should be tall enough to fit warning panel and item list"); + }, + + /** + * (Un)checks a history scope checkbox (browser & download history, + * form history, etc.). + * + * @param aPrefName + * The final portion of the checkbox's privacy.cpd.* preference name + * @param aCheckState + * True if the checkbox should be checked, false otherwise + */ + checkPrefCheckbox: function (aPrefName, aCheckState) { + var pref = "privacy.cpd." + aPrefName; + var cb = this.win.document.querySelectorAll( + "#itemList > [preference='" + pref + "']"); + is(cb.length, 1, "found checkbox for " + pref + " preference"); + if (cb[0].checked != aCheckState) + cb[0].click(); + }, + + /** + * Makes sure all the checkboxes are checked. + */ + _checkAllCheckboxesCustom: function (check) { + var cb = this.win.document.querySelectorAll("#itemList > [preference]"); + ok(cb.length > 1, "found checkboxes for preferences"); + for (var i = 0; i < cb.length; ++i) { + var pref = this.win.document.getElementById(cb[i].getAttribute("preference")); + if (!!pref.value ^ check) + cb[i].click(); + } + }, + + checkAllCheckboxes: function () { + this._checkAllCheckboxesCustom(true); + }, + + uncheckAllCheckboxes: function () { + this._checkAllCheckboxesCustom(false); + }, + + /** + * @return The details progressive disclosure button + */ + getDetailsButton: function () { + return this.win.document.getElementById("detailsExpander"); + }, + + /** + * @return The dialog's duration dropdown + */ + getDurationDropdown: function () { + return this.win.document.getElementById("sanitizeDurationChoice"); + }, + + /** + * @return The item list hidden by the details progressive disclosure button + */ + getItemList: function () { + return this.win.document.getElementById("itemList"); + }, + + /** + * @return The clear-everything warning box + */ + getWarningPanel: function () { + return this.win.document.getElementById("sanitizeEverythingWarningBox"); + }, + + /** + * @return True if the "Everything" warning panel is visible (as opposed to + * the tree) + */ + isWarningPanelVisible: function () { + return !this.getWarningPanel().hidden; + }, + + /** + * Opens the clear recent history dialog. Before calling this, set + * this.onload to a function to execute onload. It should close the dialog + * when done so that the tests may continue. Set this.onunload to a function + * to execute onunload. this.onunload is optional. If it returns true, the + * caller is expected to call waitForAsyncUpdates at some point; if false is + * returned, waitForAsyncUpdates is called automatically. + */ + open: function () { + let wh = this; + + function windowObserver(aSubject, aTopic, aData) { + if (aTopic != "domwindowopened") + return; + + Services.ww.unregisterNotification(windowObserver); + + var loaded = false; + let win = aSubject.QueryInterface(Ci.nsIDOMWindow); + + win.addEventListener("load", function onload(event) { + win.removeEventListener("load", onload, false); + + if (win.name !== "SanitizeDialog") + return; + + wh.win = win; + loaded = true; + executeSoon(() => wh.onload()); + }, false); + + win.addEventListener("unload", function onunload(event) { + if (win.name !== "SanitizeDialog") { + win.removeEventListener("unload", onunload, false); + return; + } + + // Why is unload fired before load? + if (!loaded) + return; + + win.removeEventListener("unload", onunload, false); + wh.win = win; + + // Some exceptions that reach here don't reach the test harness, but + // ok()/is() do... + Task.spawn(function* () { + if (wh.onunload) { + yield wh.onunload(); + } + yield PlacesTestUtils.promiseAsyncUpdates(); + wh._resolveClosed(); + }); + }, false); + } + Services.ww.registerNotification(windowObserver); + Services.ww.openWindow(null, + "chrome://browser/content/sanitize.xul", + "SanitizeDialog", + "chrome,titlebar,dialog,centerscreen,modal", + null); + }, + + /** + * Selects a duration in the duration dropdown. + * + * @param aDurVal + * One of the Sanitizer.TIMESPAN_* values + */ + selectDuration: function (aDurVal) { + this.getDurationDropdown().value = aDurVal; + if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) { + is(this.isWarningPanelVisible(), true, + "Warning panel should be visible for TIMESPAN_EVERYTHING"); + } + else { + is(this.isWarningPanelVisible(), false, + "Warning panel should not be visible for non-TIMESPAN_EVERYTHING"); + } + }, + + /** + * Toggles the details progressive disclosure button. + */ + toggleDetails: function () { + this.getDetailsButton().click(); + } +}; + +function promiseSanitizationComplete() { + return promiseTopicObserved("sanitizer-sanitization-complete"); +} + +/** + * Adds a download to history. + * + * @param aMinutesAgo + * The download will be downloaded this many minutes ago + */ +function* addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) { + let publicList = yield Downloads.getList(Downloads.PUBLIC); + + let name = "fakefile-" + aMinutesAgo + "-minutes-ago"; + let download = yield Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", + target: name + }); + download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin)); + download.canceled = true; + publicList.add(download); + + ok((yield downloadExists(name)), + "Sanity check: download " + name + + " should exist after creating it"); + + aExpectedPathList.push(name); +} + +/** + * Adds a form entry to history. + * + * @param aMinutesAgo + * The entry will be added this many minutes ago + */ +function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) { + let name = aMinutesAgo + "-minutes-ago"; + + // Artifically age the entry to the proper vintage. + let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin); + + return new Promise((resolve, reject) => + FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp }, + { handleError: function (error) { + reject(); + throw new Error("Error occurred updating form history: " + error); + }, + handleCompletion: function (reason) { + resolve(name); + } + }) + ) +} + +/** + * Checks if a form entry exists. + */ +function formNameExists(name) +{ + return new Promise((resolve, reject) => { + let count = 0; + FormHistory.count({ fieldname: name }, + { handleResult: result => count = result, + handleError: function (error) { + reject(error); + throw new Error("Error occurred searching form history: " + error); + }, + handleCompletion: function (reason) { + if (!reason) { + resolve(count); + } + } + }); + }); +} + +/** + * Removes all history visits, downloads, and form entries. + */ +function* blankSlate() { + let publicList = yield Downloads.getList(Downloads.PUBLIC); + let downloads = yield publicList.getAll(); + for (let download of downloads) { + yield publicList.remove(download); + yield download.finalize(true); + } + + yield new Promise((resolve, reject) => { + FormHistory.update({op: "remove"}, { + handleCompletion(reason) { + if (!reason) { + resolve(); + } + }, + handleError(error) { + reject(error); + throw new Error("Error occurred updating form history: " + error); + } + }); + }); + + yield PlacesTestUtils.clearHistory(); +} + +/** + * Ensures that the given pref is the expected value. + * + * @param aPrefName + * The pref's sub-branch under the privacy branch + * @param aExpectedVal + * The pref's expected value + * @param aMsg + * Passed to is() + */ +function boolPrefIs(aPrefName, aExpectedVal, aMsg) { + is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg); +} + +/** + * Checks to see if the download with the specified path exists. + * + * @param aPath + * The path of the download to check + * @return True if the download exists, false otherwise + */ +function* downloadExists(aPath) { + let publicList = yield Downloads.getList(Downloads.PUBLIC); + let listArray = yield publicList.getAll(); + return listArray.some(i => i.target.path == aPath); +} + +/** + * Ensures that the specified downloads are either cleared or not. + * + * @param aDownloadIDs + * Array of download database IDs + * @param aShouldBeCleared + * True if each download should be cleared, false otherwise + */ +function* ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) { + let niceStr = aShouldBeCleared ? "no longer" : "still"; + for (let id of aDownloadIDs) { + is((yield downloadExists(id)), !aShouldBeCleared, + "download " + id + " should " + niceStr + " exist"); + } +} + +/** + * Ensures that the given pref is the expected value. + * + * @param aPrefName + * The pref's sub-branch under the privacy branch + * @param aExpectedVal + * The pref's expected value + * @param aMsg + * Passed to is() + */ +function intPrefIs(aPrefName, aExpectedVal, aMsg) { + is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg); +} + +/** + * Creates a visit time. + * + * @param aMinutesAgo + * The visit will be visited this many minutes ago + */ +function visitTimeForMinutesAgo(aMinutesAgo) { + return now_uSec - aMinutesAgo * kUsecPerMin; +} diff --git a/browser/base/content/test/general/browser_save_link-perwindowpb.js b/browser/base/content/test/general/browser_save_link-perwindowpb.js new file mode 100644 index 000000000..5c99ba32a --- /dev/null +++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js @@ -0,0 +1,199 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +// Trigger a save of a link in public mode, then trigger an identical save +// in private mode and ensure that the second request is differentiated from +// the first by checking that cookies set by the first response are not sent +// during the second request. +function triggerSave(aWindow, aCallback) { + info("started triggerSave"); + var fileName; + let testBrowser = aWindow.gBrowser.selectedBrowser; + // This page sets a cookie if and only if a cookie does not exist yet + let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517-2.html"; + testBrowser.loadURI(testURI); + BrowserTestUtils.browserLoaded(testBrowser, false, testURI) + .then(() => { + waitForFocus(function () { + info("register to handle popupshown"); + aWindow.document.addEventListener("popupshown", contextMenuOpened, false); + + BrowserTestUtils.synthesizeMouseAtCenter("#fff", {type: "contextmenu", button: 2}, testBrowser); + info("right clicked!"); + }, aWindow); + }); + + function contextMenuOpened(event) { + info("contextMenuOpened"); + aWindow.document.removeEventListener("popupshown", contextMenuOpened); + + // Create the folder the link will be saved into. + var destDir = createTemporarySaveDirectory(); + var destFile = destDir.clone(); + + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + info("showCallback"); + fileName = fp.defaultString; + info("fileName: " + fileName); + destFile.append (fileName); + MockFilePicker.returnFiles = [destFile]; + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + info("done showCallback"); + }; + + mockTransferCallback = function(downloadSuccess) { + info("mockTransferCallback"); + onTransferComplete(aWindow, downloadSuccess, destDir); + destDir.remove(true); + ok(!destDir.exists(), "Destination dir should be removed"); + ok(!destFile.exists(), "Destination file should be removed"); + mockTransferCallback = null; + info("done mockTransferCallback"); + } + + // Select "Save Link As" option from context menu + var saveLinkCommand = aWindow.document.getElementById("context-savelink"); + info("saveLinkCommand: " + saveLinkCommand); + saveLinkCommand.doCommand(); + + event.target.hidePopup(); + info("popup hidden"); + } + + function onTransferComplete(aWindow2, downloadSuccess, destDir) { + ok(downloadSuccess, "Link should have been downloaded successfully"); + aWindow2.close(); + + executeSoon(() => aCallback()); + } +} + +function test() { + info("Start the test"); + waitForExplicitFinish(); + + var gNumSet = 0; + function testOnWindow(options, callback) { + info("testOnWindow(" + options + ")"); + var win = OpenBrowserWindow(options); + info("got " + win); + whenDelayedStartupFinished(win, () => callback(win)); + } + + function whenDelayedStartupFinished(aWindow, aCallback) { + info("whenDelayedStartupFinished"); + Services.obs.addObserver(function obs(aSubject, aTopic) { + info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow); + if (aWindow == aSubject) { + Services.obs.removeObserver(obs, aTopic); + executeSoon(aCallback); + info("whenDelayedStartupFinished found our window"); + } + }, "browser-delayed-startup-finished", false); + } + + mockTransferRegisterer.register(); + + registerCleanupFunction(function () { + info("Running the cleanup code"); + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + Services.obs.removeObserver(observer, "http-on-modify-request"); + Services.obs.removeObserver(observer, "http-on-examine-response"); + info("Finished running the cleanup code"); + }); + + function observer(subject, topic, state) { + info("observer called with " + topic); + if (topic == "http-on-modify-request") { + onModifyRequest(subject); + } else if (topic == "http-on-examine-response") { + onExamineResponse(subject); + } + } + + function onExamineResponse(subject) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + info("onExamineResponse with " + channel.URI.spec); + if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") { + info("returning"); + return; + } + try { + let cookies = channel.getResponseHeader("set-cookie"); + // From browser/base/content/test/general/bug792715.sjs, we receive a Set-Cookie + // header with foopy=1 when there are no cookies for that domain. + is(cookies, "foopy=1", "Cookie should be foopy=1"); + gNumSet += 1; + info("gNumSet = " + gNumSet); + } catch (ex) { + if (ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { + info("onExamineResponse caught NOTAVAIL" + ex); + } else { + info("ionExamineResponse caught " + ex); + } + } + } + + function onModifyRequest(subject) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + info("onModifyRequest with " + channel.URI.spec); + if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") { + return; + } + try { + let cookies = channel.getRequestHeader("cookie"); + info("cookies: " + cookies); + // From browser/base/content/test/general/bug792715.sjs, we should never send a + // cookie because we are making only 2 requests: one in public mode, and + // one in private mode. + throw "We should never send a cookie in this test"; + } catch (ex) { + if (ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { + info("onModifyRequest caught NOTAVAIL" + ex); + } else { + info("ionModifyRequest caught " + ex); + } + } + } + + Services.obs.addObserver(observer, "http-on-modify-request", false); + Services.obs.addObserver(observer, "http-on-examine-response", false); + + testOnWindow(undefined, function(win) { + // The first save from a regular window sets a cookie. + triggerSave(win, function() { + is(gNumSet, 1, "1 cookie should be set"); + + // The second save from a private window also sets a cookie. + testOnWindow({private: true}, function(win2) { + triggerSave(win2, function() { + is(gNumSet, 2, "2 cookies should be set"); + finish(); + }); + }); + }); + }); +} + +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); + +function createTemporarySaveDirectory() { + var saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + info("create testsavedir!"); + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + info("return from createTempSaveDir: " + saveDir.path); + return saveDir; +} diff --git a/browser/base/content/test/general/browser_save_link_when_window_navigates.js b/browser/base/content/test/general/browser_save_link_when_window_navigates.js new file mode 100644 index 000000000..2fd10b00e --- /dev/null +++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js @@ -0,0 +1,173 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite"; +const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir"; +const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul"; + +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); + +function createTemporarySaveDirectory() { + var saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + info("create testsavedir!"); + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + info("return from createTempSaveDir: " + saveDir.path); + return saveDir; +} + +function triggerSave(aWindow, aCallback) { + info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off")); + var fileName; + let testBrowser = aWindow.gBrowser.selectedBrowser; + let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html"; + windowObserver.setCallback(onUCTDialog); + testBrowser.loadURI(testURI); + + // Create the folder the link will be saved into. + var destDir = createTemporarySaveDirectory(); + var destFile = destDir.clone(); + + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + info("showCallback"); + fileName = fp.defaultString; + info("fileName: " + fileName); + destFile.append (fileName); + MockFilePicker.returnFiles = [destFile]; + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + info("done showCallback"); + }; + + mockTransferCallback = function(downloadSuccess) { + info("mockTransferCallback"); + onTransferComplete(aWindow, downloadSuccess, destDir); + destDir.remove(true); + ok(!destDir.exists(), "Destination dir should be removed"); + ok(!destFile.exists(), "Destination file should be removed"); + mockTransferCallback = null; + info("done mockTransferCallback"); + } + + function onUCTDialog(dialog) { + function doLoad() { + content.document.querySelector('iframe').remove(); + } + testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false); + executeSoon(continueDownloading); + } + + function continueDownloading() { + let windows = Services.wm.getEnumerator(""); + while (windows.hasMoreElements()) { + let win = windows.getNext(); + if (win.location && win.location.href == UCT_URI) { + win.document.documentElement._fireButtonEvent("accept"); + win.close(); + return; + } + } + ok(false, "No Unknown Content Type dialog yet?"); + } + + function onTransferComplete(aWindow2, downloadSuccess) { + ok(downloadSuccess, "Link should have been downloaded successfully"); + aWindow2.close(); + + executeSoon(aCallback); + } +} + + +var windowObserver = { + setCallback: function(aCallback) { + if (this._callback) { + ok(false, "Should only be dealing with one callback at a time."); + } + this._callback = aCallback; + }, + observe: function(aSubject, aTopic, aData) { + if (aTopic != "domwindowopened") { + return; + } + + let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget); + + win.addEventListener("load", function onLoad(event) { + win.removeEventListener("load", onLoad, false); + + if (win.location == UCT_URI) { + SimpleTest.executeSoon(function() { + if (windowObserver._callback) { + windowObserver._callback(win); + delete windowObserver._callback; + } else { + ok(false, "Unexpected UCT dialog!"); + } + }); + } + }, false); + } +}; + +Services.ww.registerNotification(windowObserver); + +function test() { + waitForExplicitFinish(); + + function testOnWindow(options, callback) { + info("testOnWindow(" + options + ")"); + var win = OpenBrowserWindow(options); + info("got " + win); + whenDelayedStartupFinished(win, () => callback(win)); + } + + function whenDelayedStartupFinished(aWindow, aCallback) { + info("whenDelayedStartupFinished"); + Services.obs.addObserver(function observer(aSubject, aTopic) { + info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow); + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + executeSoon(aCallback); + info("whenDelayedStartupFinished found our window"); + } + }, "browser-delayed-startup-finished", false); + } + + mockTransferRegisterer.register(); + + registerCleanupFunction(function () { + info("Running the cleanup code"); + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + Services.ww.unregisterNotification(windowObserver); + Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF); + Services.prefs.clearUserPref(SAVE_PER_SITE_PREF); + info("Finished running the cleanup code"); + }); + + Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false); + testOnWindow(undefined, function(win) { + let windowGonePromise = promiseWindowWillBeClosed(win); + Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true); + triggerSave(win, function() { + windowGonePromise.then(function() { + Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false); + testOnWindow(undefined, function(win2) { + triggerSave(win2, finish); + }); + }); + }); + }); +} + diff --git a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js new file mode 100644 index 000000000..e7ed5fa34 --- /dev/null +++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function createTemporarySaveDirectory() { + var saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + return saveDir; +} + +function promiseNoCacheEntry(filename) { + return new Promise((resolve, reject) => { + Visitor.prototype = { + onCacheStorageInfo: function(num, consumption) + { + info("disk storage contains " + num + " entries"); + }, + onCacheEntryInfo: function(uri) + { + let urispec = uri.asciiSpec; + info(urispec); + is(urispec.includes(filename), false, "web content present in disk cache"); + }, + onCacheEntryVisitCompleted: function() + { + resolve(); + } + }; + function Visitor() {} + + let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"] + .getService(Ci.nsICacheStorageService); + let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null); + let storage = cache.diskCacheStorage(LoadContextInfo.default, false); + storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */); + }); +} + +function promiseImageDownloaded() { + return new Promise((resolve, reject) => { + let fileName; + let MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window); + + function onTransferComplete(downloadSuccess) { + ok(downloadSuccess, "Image file should have been downloaded successfully " + fileName); + + // Give the request a chance to finish and create a cache entry + resolve(fileName); + } + + // Create the folder the image will be saved into. + var destDir = createTemporarySaveDirectory(); + var destFile = destDir.clone(); + + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + fileName = fp.defaultString; + destFile.append (fileName); + MockFilePicker.returnFiles = [destFile]; + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; + + mockTransferCallback = onTransferComplete; + mockTransferRegisterer.register(); + + registerCleanupFunction(function () { + mockTransferCallback = null; + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + destDir.remove(true); + }); + + }); +} + +add_task(function* () { + let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html"; + let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + let tab = yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, testURI); + + let contextMenu = privateWindow.document.getElementById("contentAreaContextMenu"); + let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + yield BrowserTestUtils.synthesizeMouseAtCenter("#img", { + type: "contextmenu", + button: 2 + }, tab.linkedBrowser); + yield popupShown; + + let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"] + .getService(Ci.nsICacheStorageService); + cache.clear(); + + let imageDownloaded = promiseImageDownloaded(); + // Select "Save Image As" option from context menu + privateWindow.document.getElementById("context-saveimage").doCommand(); + + contextMenu.hidePopup(); + yield popupHidden; + + // wait for image download + let fileName = yield imageDownloaded; + yield promiseNoCacheEntry(fileName); + + yield BrowserTestUtils.closeWindow(privateWindow); +}); + +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); diff --git a/browser/base/content/test/general/browser_save_video.js b/browser/base/content/test/general/browser_save_video.js new file mode 100644 index 000000000..e81286b7a --- /dev/null +++ b/browser/base/content/test/general/browser_save_video.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/** + * TestCase for bug 564387 + * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387> + */ +add_task(function* () { + var fileName; + + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html"); + yield loadPromise; + + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#video1", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser); + info("context menu click on video1"); + + yield popupShownPromise; + + info("context menu opened on video1"); + + // Create the folder the video will be saved into. + var destDir = createTemporarySaveDirectory(); + var destFile = destDir.clone(); + + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + fileName = fp.defaultString; + destFile.append(fileName); + MockFilePicker.returnFiles = [destFile]; + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; + + let transferCompletePromise = new Promise((resolve) => { + function onTransferComplete(downloadSuccess) { + ok(downloadSuccess, "Video file should have been downloaded successfully"); + + is(fileName, "web-video1-expectedName.ogv", + "Video file name is correctly retrieved from Content-Disposition http header"); + resolve(); + } + + mockTransferCallback = onTransferComplete; + mockTransferRegisterer.register(); + }); + + registerCleanupFunction(function () { + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + destDir.remove(true); + }); + + // Select "Save Video As" option from context menu + var saveVideoCommand = document.getElementById("context-savevideo"); + saveVideoCommand.doCommand(); + info("context-savevideo command executed"); + + let contextMenu = document.getElementById("contentAreaContextMenu"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + contextMenu.hidePopup(); + yield popupHiddenPromise; + + yield transferCompletePromise; +}); + + +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); + +function createTemporarySaveDirectory() { + var saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + return saveDir; +} diff --git a/browser/base/content/test/general/browser_save_video_frame.js b/browser/base/content/test/general/browser_save_video_frame.js new file mode 100644 index 000000000..e9b8a0475 --- /dev/null +++ b/browser/base/content/test/general/browser_save_video_frame.js @@ -0,0 +1,125 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const VIDEO_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html"; + +/** + * mockTransfer.js provides a utility that lets us mock out + * the "Save File" dialog. + */ +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); + +/** + * Creates and returns an nsIFile for a new temporary save + * directory. + * + * @return nsIFile + */ +function createTemporarySaveDirectory() { + let saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + return saveDir; +} +/** + * MockTransfer exposes a "mockTransferCallback" global which + * allows us to define a callback to be called once the mock file + * selector has selected where to save the file. + */ +function waitForTransferComplete() { + return new Promise((resolve) => { + mockTransferCallback = () => { + ok(true, "Transfer completed"); + resolve(); + } + }); +} + +/** + * Given some browser, loads a framescript that right-clicks + * on the video1 element to spawn a contextmenu. + */ +function rightClickVideo(browser) { + let frame_script = () => { + const Ci = Components.interfaces; + let utils = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + let document = content.document; + let video = document.getElementById("video1"); + let rect = video.getBoundingClientRect(); + + /* Synthesize a click in the center of the video. */ + let left = rect.left + (rect.width / 2); + let top = rect.top + (rect.height / 2); + + utils.sendMouseEvent("contextmenu", left, top, + 2, /* aButton */ + 1, /* aClickCount */ + 0 /* aModifiers */); + }; + let mm = browser.messageManager; + mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true); +} + +/** + * Loads a page with a <video> element, right-clicks it and chooses + * to save a frame screenshot to the disk. Completes once we've + * verified that the frame has been saved to disk. + */ +add_task(function*() { + let MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window); + + // Create the folder the video will be saved into. + let destDir = createTemporarySaveDirectory(); + let destFile = destDir.clone(); + + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + destFile.append(fp.defaultString); + MockFilePicker.returnFiles = [destFile]; + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; + + mockTransferRegisterer.register(); + + // Make sure that we clean these things up when we're done. + registerCleanupFunction(function () { + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + destDir.remove(true); + }); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + info("Loading video tab"); + yield promiseTabLoadEvent(tab, VIDEO_URL); + info("Video tab loaded."); + + let context = document.getElementById("contentAreaContextMenu"); + let popupPromise = promisePopupShown(context); + + info("Synthesizing right-click on video element"); + rightClickVideo(browser); + info("Waiting for popup to fire popupshown."); + yield popupPromise; + info("Popup fired popupshown"); + + let saveSnapshotCommand = document.getElementById("context-video-saveimage"); + let promiseTransfer = waitForTransferComplete() + info("Firing save snapshot command"); + saveSnapshotCommand.doCommand(); + context.hidePopup(); + info("Waiting for transfer completion"); + yield promiseTransfer; + info("Transfer complete"); + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_scope.js b/browser/base/content/test/general/browser_scope.js new file mode 100644 index 000000000..f8141e5f6 --- /dev/null +++ b/browser/base/content/test/general/browser_scope.js @@ -0,0 +1,10 @@ +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null"); + +function test() { + ok(!!gBrowser, "gBrowser exists"); + is(gBrowser, getBrowser(), "both ways of getting tabbrowser work"); +} diff --git a/browser/base/content/test/general/browser_selectTabAtIndex.js b/browser/base/content/test/general/browser_selectTabAtIndex.js new file mode 100644 index 000000000..b6578aec0 --- /dev/null +++ b/browser/base/content/test/general/browser_selectTabAtIndex.js @@ -0,0 +1,81 @@ +"use strict"; + +function test() { + const isLinux = navigator.platform.indexOf("Linux") == 0; + + function assertTab(expectedTab) { + is(gBrowser.tabContainer.selectedIndex, expectedTab, + `tab index ${expectedTab} should be selected`); + } + + function sendAccelKey(key) { + // Make sure the keystroke goes to chrome. + document.activeElement.blur(); + EventUtils.synthesizeKey(key.toString(), { altKey: isLinux, accelKey: !isLinux }); + } + + function createTabs(count) { + for (let n = 0; n < count; n++) + gBrowser.addTab(); + } + + function testKey(key, expectedTab) { + sendAccelKey(key); + assertTab(expectedTab); + } + + function testIndex(index, expectedTab) { + gBrowser.selectTabAtIndex(index); + assertTab(expectedTab); + } + + // Create fewer tabs than our 9 number keys. + is(gBrowser.tabs.length, 1, "should have 1 tab"); + createTabs(4); + is(gBrowser.tabs.length, 5, "should have 5 tabs"); + + // Test keyboard shortcuts. Order tests so that no two test cases have the + // same expected tab in a row. This ensures that tab selection actually + // changed the selected tab. + testKey(8, 4); + testKey(1, 0); + testKey(2, 1); + testKey(4, 3); + testKey(9, 4); + + // Test index selection. + testIndex(0, 0); + testIndex(4, 4); + testIndex(-5, 0); + testIndex(5, 4); + testIndex(-4, 1); + testIndex(1, 1); + testIndex(-1, 4); + testIndex(9, 4); + + // Create more tabs than our 9 number keys. + createTabs(10); + is(gBrowser.tabs.length, 15, "should have 15 tabs"); + + // Test keyboard shortcuts. + testKey(2, 1); + testKey(1, 0); + testKey(4, 3); + testKey(8, 7); + testKey(9, 14); + + // Test index selection. + testIndex(-15, 0); + testIndex(14, 14); + testIndex(-14, 1); + testIndex(15, 14); + testIndex(-1, 14); + testIndex(0, 0); + testIndex(1, 1); + testIndex(9, 9); + + // Clean up tabs. + for (let n = 15; n > 1; n--) + gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true}); + is(gBrowser.tabs.length, 1, "should have 1 tab"); +} diff --git a/browser/base/content/test/general/browser_selectpopup.js b/browser/base/content/test/general/browser_selectpopup.js new file mode 100644 index 000000000..d34254d1c --- /dev/null +++ b/browser/base/content/test/general/browser_selectpopup.js @@ -0,0 +1,563 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test checks that a <select> with an <optgroup> opens and can be navigated +// in a child process. This is different than single-process as a <menulist> is used +// to implement the dropdown list. + +requestLongerTimeout(2); + +const XHTML_DTD = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'; + +const PAGECONTENT = + "<html xmlns='http://www.w3.org/1999/xhtml'>" + + "<body onload='gChangeEvents = 0;gInputEvents = 0; document.body.firstChild.focus()'><select oninput='gInputEvents++' onchange='gChangeEvents++'>" + + " <optgroup label='First Group'>" + + " <option value='One'>One</option>" + + " <option value='Two'>Two</option>" + + " </optgroup>" + + " <option value='Three'>Three</option>" + + " <optgroup label='Second Group' disabled='true'>" + + " <option value='Four'>Four</option>" + + " <option value='Five'>Five</option>" + + " </optgroup>" + + " <option value='Six' disabled='true'>Six</option>" + + " <optgroup label='Third Group'>" + + " <option value='Seven'> Seven </option>" + + " <option value='Eight'> Eight </option>" + + " </optgroup></select><input />Text" + + "</body></html>"; + +const PAGECONTENT_SMALL = + "<html>" + + "<body><select id='one'>" + + " <option value='One'>One</option>" + + " <option value='Two'>Two</option>" + + "</select><select id='two'>" + + " <option value='Three'>Three</option>" + + " <option value='Four'>Four</option>" + + "</select><select id='three'>" + + " <option value='Five'>Five</option>" + + " <option value='Six'>Six</option>" + + "</select></body></html>"; + +const PAGECONTENT_SOMEHIDDEN = + "<html><head><style>.hidden { display: none; }</style></head>" + + "<body><select id='one'>" + + " <option value='One' style='display: none;'>OneHidden</option>" + + " <option value='Two' class='hidden'>TwoHidden</option>" + + " <option value='Three'>ThreeVisible</option>" + + " <option value='Four'style='display: table;'>FourVisible</option>" + + " <option value='Five'>FiveVisible</option>" + + " <optgroup label='GroupHidden' class='hidden'>" + + " <option value='Four'>Six.OneHidden</option>" + + " <option value='Five' style='display: block;'>Six.TwoHidden</option>" + + " </optgroup>" + + " <option value='Six' class='hidden' style='display: block;'>SevenVisible</option>" + + "</select></body></html>"; + +const PAGECONTENT_TRANSLATED = + "<html><body>" + + "<div id='div'>" + + "<iframe id='frame' width='320' height='295' style='border: none;'" + + " src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" + + "</iframe>" + + "</div></body></html>"; + +function openSelectPopup(selectPopup, withMouse, selector = "select", win = window) +{ + let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown"); + + if (withMouse) { + return Promise.all([popupShownPromise, + BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser)]); + } + + EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }, win); + return popupShownPromise; +} + +function hideSelectPopup(selectPopup, mode = "enter", win = window) +{ + let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden"); + + if (mode == "escape") { + EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" }, win); + } + else if (mode == "enter") { + EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" }, win); + } + else if (mode == "click") { + EventUtils.synthesizeMouseAtCenter(selectPopup.lastChild, { }, win); + } + + return popupHiddenPromise; +} + +function getInputEvents() +{ + return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { + return content.wrappedJSObject.gInputEvents; + }); +} + +function getChangeEvents() +{ + return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { + return content.wrappedJSObject.gChangeEvents; + }); +} + +function* doSelectTests(contentType, dtd) +{ + const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + let menulist = document.getElementById("ContentSelectDropdown"); + let selectPopup = menulist.menupopup; + + yield openSelectPopup(selectPopup); + + let isWindows = navigator.platform.indexOf("Win") >= 0; + + is(menulist.selectedIndex, 1, "Initial selection"); + is(selectPopup.firstChild.localName, "menucaption", "optgroup is caption"); + is(selectPopup.firstChild.getAttribute("label"), "First Group", "optgroup label"); + is(selectPopup.childNodes[1].localName, "menuitem", "option is menuitem"); + is(selectPopup.childNodes[1].getAttribute("label"), "One", "option label"); + + EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); + is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(2), "Select item 2"); + is(menulist.selectedIndex, isWindows ? 2 : 1, "Select item 2 selectedIndex"); + + EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); + is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3"); + is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex"); + + EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); + + // On Windows, one can navigate on disabled menuitems + is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(9), + "Skip optgroup header and disabled items select item 7"); + is(menulist.selectedIndex, isWindows ? 9 : 1, "Select or skip disabled item selectedIndex"); + + for (let i = 0; i < 10; i++) { + is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled") + } + + EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" }); + is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again"); + is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex"); + + is((yield getInputEvents()), 0, "Before closed - number of input events"); + is((yield getChangeEvents()), 0, "Before closed - number of change events"); + + EventUtils.synthesizeKey("a", { accelKey: true }); + yield ContentTask.spawn(gBrowser.selectedBrowser, { isWindows }, function(args) { + Assert.equal(String(content.getSelection()), args.isWindows ? "Text" : "", + "Select all while popup is open"); + }); + + // Backspace should not go back + let handleKeyPress = function(event) { + ok(false, "Should not get keypress event"); + } + window.addEventListener("keypress", handleKeyPress); + EventUtils.synthesizeKey("VK_BACK_SPACE", { }); + window.removeEventListener("keypress", handleKeyPress); + + yield hideSelectPopup(selectPopup); + + is(menulist.selectedIndex, 3, "Item 3 still selected"); + is((yield getInputEvents()), 1, "After closed - number of input events"); + is((yield getChangeEvents()), 1, "After closed - number of change events"); + + // Opening and closing the popup without changing the value should not fire a change event. + yield openSelectPopup(selectPopup, true); + yield hideSelectPopup(selectPopup, "escape"); + is((yield getInputEvents()), 1, "Open and close with no change - number of input events"); + is((yield getChangeEvents()), 1, "Open and close with no change - number of change events"); + EventUtils.synthesizeKey("VK_TAB", { }); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + is((yield getInputEvents()), 1, "Tab away from select with no change - number of input events"); + is((yield getChangeEvents()), 1, "Tab away from select with no change - number of change events"); + + yield openSelectPopup(selectPopup, true); + EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); + yield hideSelectPopup(selectPopup, "escape"); + is((yield getInputEvents()), isWindows ? 2 : 1, "Open and close with change - number of input events"); + is((yield getChangeEvents()), isWindows ? 2 : 1, "Open and close with change - number of change events"); + EventUtils.synthesizeKey("VK_TAB", { }); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + is((yield getInputEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of input events"); + is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events"); + + is(selectPopup.lastChild.previousSibling.label, "Seven", "Spaces collapsed"); + is(selectPopup.lastChild.label, "\xA0\xA0Eight\xA0\xA0", "Non-breaking spaces not collapsed"); + + yield BrowserTestUtils.removeTab(tab); +} + +add_task(function*() { + yield doSelectTests("text/html", ""); +}); + +add_task(function*() { + yield doSelectTests("application/xhtml+xml", XHTML_DTD); +}); + +// This test opens a select popup and removes the content node of a popup while +// The popup should close if its node is removed. +add_task(function*() { + const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + let menulist = document.getElementById("ContentSelectDropdown"); + let selectPopup = menulist.menupopup; + + // First, try it when a different <select> element than the one that is open is removed + yield openSelectPopup(selectPopup, true, "#one"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { + content.document.body.removeChild(content.document.getElementById("two")); + }); + + // Wait a bit just to make sure the popup won't close. + yield new Promise(resolve => setTimeout(resolve, 1000)); + + is(selectPopup.state, "open", "Different popup did not affect open popup"); + + yield hideSelectPopup(selectPopup); + + // Next, try it when the same <select> element than the one that is open is removed + yield openSelectPopup(selectPopup, true, "#three"); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden"); + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { + content.document.body.removeChild(content.document.getElementById("three")); + }); + yield popupHiddenPromise; + + ok(true, "Popup hidden when select is removed"); + + // Finally, try it when the tab is closed while the select popup is open. + yield openSelectPopup(selectPopup, true, "#one"); + + popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden"); + yield BrowserTestUtils.removeTab(tab); + yield popupHiddenPromise; + + ok(true, "Popup hidden when tab is closed"); +}); + +// This test opens a select popup that is isn't a frame and has some translations applied. +add_task(function*() { + const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + let menulist = document.getElementById("ContentSelectDropdown"); + let selectPopup = menulist.menupopup; + + // First, get the position of the select popup when no translations have been applied. + yield openSelectPopup(selectPopup, false); + + let rect = selectPopup.getBoundingClientRect(); + let expectedX = rect.left; + let expectedY = rect.top; + + yield hideSelectPopup(selectPopup); + + // Iterate through a set of steps which each add more translation to the select's expected position. + let steps = [ + [ "div", "transform: translateX(7px) translateY(13px);", 7, 13 ], + [ "frame", "border-top: 5px solid green; border-left: 10px solid red; border-right: 35px solid blue;", 10, 5 ], + [ "frame", "border: none; padding-left: 6px; padding-right: 12px; padding-top: 2px;", -4, -3 ], + [ "select", "margin: 9px; transform: translateY(-3px);", 9, 6 ], + ]; + + for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) { + let step = steps[stepIndex]; + + yield ContentTask.spawn(gBrowser.selectedBrowser, step, function*(contentStep) { + return new Promise(resolve => { + let changedWin = content; + + let elem; + if (contentStep[0] == "select") { + changedWin = content.document.getElementById("frame").contentWindow; + elem = changedWin.document.getElementById("select"); + } + else { + elem = content.document.getElementById(contentStep[0]); + } + + changedWin.addEventListener("MozAfterPaint", function onPaint() { + changedWin.removeEventListener("MozAfterPaint", onPaint); + resolve(); + }); + + elem.style = contentStep[1]; + elem.getBoundingClientRect(); + }); + }); + + yield openSelectPopup(selectPopup, false); + + expectedX += step[2]; + expectedY += step[3]; + + let popupRect = selectPopup.getBoundingClientRect(); + is(popupRect.left, expectedX, "step " + (stepIndex + 1) + " x"); + is(popupRect.top, expectedY, "step " + (stepIndex + 1) + " y"); + + yield hideSelectPopup(selectPopup); + } + + yield BrowserTestUtils.removeTab(tab); +}); + +// Test that we get the right events when a select popup is changed. +add_task(function* test_event_order() { + const URL = "data:text/html," + escape(PAGECONTENT_SMALL); + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: URL, + }, function*(browser) { + let menulist = document.getElementById("ContentSelectDropdown"); + let selectPopup = menulist.menupopup; + + // According to https://html.spec.whatwg.org/#the-select-element, + // we want to fire input, change, and then click events on the + // <select> (in that order) when it has changed. + let expectedEnter = [ + { + type: "input", + cancelable: false, + targetIsOption: false, + }, + { + type: "change", + cancelable: false, + targetIsOption: false, + }, + ]; + + let expectedClick = [ + { + type: "mousedown", + cancelable: true, + targetIsOption: true, + }, + { + type: "mouseup", + cancelable: true, + targetIsOption: true, + }, + { + type: "input", + cancelable: false, + targetIsOption: false, + }, + { + type: "change", + cancelable: false, + targetIsOption: false, + }, + { + type: "click", + cancelable: true, + targetIsOption: true, + }, + ]; + + for (let mode of ["enter", "click"]) { + let expected = mode == "enter" ? expectedEnter : expectedClick; + yield openSelectPopup(selectPopup, true, mode == "enter" ? "#one" : "#two"); + + let eventsPromise = ContentTask.spawn(browser, [mode, expected], function*([contentMode, contentExpected]) { + return new Promise((resolve) => { + function onEvent(event) { + select.removeEventListener(event.type, onEvent); + Assert.ok(contentExpected.length, "Unexpected event " + event.type); + let expectation = contentExpected.shift(); + Assert.equal(event.type, expectation.type, + "Expected the right event order"); + Assert.ok(event.bubbles, "All of these events should bubble"); + Assert.equal(event.cancelable, expectation.cancelable, + "Cancellation property should match"); + Assert.equal(event.target.localName, + expectation.targetIsOption ? "option" : "select", + "Target matches"); + if (!contentExpected.length) { + resolve(); + } + } + + let select = content.document.getElementById(contentMode == "enter" ? "one" : "two"); + for (let event of ["input", "change", "mousedown", "mouseup", "click"]) { + select.addEventListener(event, onEvent); + } + }); + }); + + EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); + yield hideSelectPopup(selectPopup, mode); + yield eventsPromise; + } + }); +}); + +function* performLargePopupTests(win) +{ + let browser = win.gBrowser.selectedBrowser; + + yield ContentTask.spawn(browser, null, function*() { + let doc = content.document; + let select = doc.getElementById("one"); + for (var i = 0; i < 180; i++) { + select.add(new content.Option("Test" + i)); + } + + select.options[60].selected = true; + select.focus(); + }); + + let selectPopup = win.document.getElementById("ContentSelectDropdown").menupopup; + let browserRect = browser.getBoundingClientRect(); + + let positions = [ + "margin-top: 300px;", + "position: fixed; bottom: 100px;", + "width: 100%; height: 9999px;" + ]; + + let position; + while (true) { + yield openSelectPopup(selectPopup, false, "select", win); + + let rect = selectPopup.getBoundingClientRect(); + ok(rect.top >= browserRect.top, "Popup top position in within browser area"); + ok(rect.bottom <= browserRect.bottom, "Popup bottom position in within browser area"); + + // Don't check the scroll position for the last step as the popup will be cut off. + if (positions.length > 0) { + let cs = win.getComputedStyle(selectPopup); + let bpBottom = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth); + + is(selectPopup.childNodes[60].getBoundingClientRect().bottom, + selectPopup.getBoundingClientRect().bottom - bpBottom, + "Popup scroll at correct position " + bpBottom); + } + + yield hideSelectPopup(selectPopup, "enter", win); + + position = positions.shift(); + if (!position) { + break; + } + + let contentPainted = BrowserTestUtils.contentPainted(browser); + yield ContentTask.spawn(browser, position, function*(contentPosition) { + let select = content.document.getElementById("one"); + select.setAttribute("style", contentPosition); + select.getBoundingClientRect(); + }); + yield contentPainted; + } +} + +// This test checks select elements with a large number of options to ensure that +// the popup appears within the browser area. +add_task(function* test_large_popup() { + const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + yield* performLargePopupTests(window); + + yield BrowserTestUtils.removeTab(tab); +}); + +// This test checks the same as the previous test but in a new smaller window. +add_task(function* test_large_popup_in_small_window() { + let newwin = yield BrowserTestUtils.openNewBrowserWindow({ width: 400, height: 400 }); + + const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL); + let browserLoadedPromise = BrowserTestUtils.browserLoaded(newwin.gBrowser.selectedBrowser); + yield BrowserTestUtils.loadURI(newwin.gBrowser.selectedBrowser, pageUrl); + yield browserLoadedPromise; + + newwin.gBrowser.selectedBrowser.focus(); + + yield* performLargePopupTests(newwin); + + yield BrowserTestUtils.closeWindow(newwin); +}); + +// This test checks that a mousemove event is fired correctly at the menu and +// not at the browser, ensuring that any mouse capture has been cleared. +add_task(function* test_mousemove_correcttarget() { + const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + let selectPopup = document.getElementById("ContentSelectDropdown").menupopup; + + let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown"); + yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser); + yield popupShownPromise; + + yield new Promise(resolve => { + window.addEventListener("mousemove", function checkForMouseMove(event) { + window.removeEventListener("mousemove", checkForMouseMove, true); + is(event.target.localName.indexOf("menu"), 0, "mouse over menu"); + resolve(); + }, true); + + EventUtils.synthesizeMouseAtCenter(selectPopup.firstChild, { type: "mousemove" }); + }); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mouseup" }, gBrowser.selectedBrowser); + + yield hideSelectPopup(selectPopup); + + // The popup should be closed when fullscreen mode is entered or exited. + for (let steps = 0; steps < 2; steps++) { + yield openSelectPopup(selectPopup, true); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden"); + let sizeModeChanged = BrowserTestUtils.waitForEvent(window, "sizemodechange"); + BrowserFullScreen(); + yield sizeModeChanged; + yield popupHiddenPromise; + } + + yield BrowserTestUtils.removeTab(tab); +}); + +// This test checks when a <select> element has some options with altered display values. +add_task(function* test_somehidden() { + const pageUrl = "data:text/html," + escape(PAGECONTENT_SOMEHIDDEN); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + let selectPopup = document.getElementById("ContentSelectDropdown").menupopup; + + let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown"); + yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser); + yield popupShownPromise; + + // The exact number is not needed; just ensure the height is larger than 4 items to accomodate any popup borders. + ok(selectPopup.getBoundingClientRect().height >= selectPopup.lastChild.getBoundingClientRect().height * 4, "Height contains at least 4 items"); + ok(selectPopup.getBoundingClientRect().height < selectPopup.lastChild.getBoundingClientRect().height * 5, "Height doesn't contain 5 items"); + + // The label contains the substring 'Visible' for items that are visible. + // Otherwise, it is expected to be display: none. + is(selectPopup.parentNode.itemCount, 9, "Correct number of items"); + let child = selectPopup.firstChild; + let idx = 1; + while (child) { + is(getComputedStyle(child).display, child.label.indexOf("Visible") > 0 ? "-moz-box" : "none", + "Item " + (idx++) + " is visible"); + child = child.nextSibling; + } + + yield hideSelectPopup(selectPopup, "escape"); + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser_ssl_error_reports.js b/browser/base/content/test/general/browser_ssl_error_reports.js new file mode 100644 index 000000000..b1b1c8b84 --- /dev/null +++ b/browser/base/content/test/general/browser_ssl_error_reports.js @@ -0,0 +1,174 @@ +"use strict"; + +const URL_REPORTS = "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?"; +const URL_BAD_CHAIN = "https://badchain.include-subdomains.pinning.example.com/"; +const URL_NO_CERT = "https://fail-handshake.example.com/"; +const URL_BAD_CERT = "https://expired.example.com/"; +const URL_BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443/"; +const ROOT = getRootDirectory(gTestPath); +const PREF_REPORT_ENABLED = "security.ssl.errorReporting.enabled"; +const PREF_REPORT_AUTOMATIC = "security.ssl.errorReporting.automatic"; +const PREF_REPORT_URL = "security.ssl.errorReporting.url"; + +SimpleTest.requestCompleteLog(); + +Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2); + +function cleanup() { + Services.prefs.clearUserPref(PREF_REPORT_ENABLED); + Services.prefs.clearUserPref(PREF_REPORT_AUTOMATIC); + Services.prefs.clearUserPref(PREF_REPORT_URL); +} + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.cert_pinning.enforcement_level"); + cleanup(); +}); + +add_task(function* test_send_report_neterror() { + yield testSendReportAutomatically(URL_BAD_CHAIN, "succeed", "neterror"); + yield testSendReportAutomatically(URL_NO_CERT, "nocert", "neterror"); + yield testSetAutomatic(URL_NO_CERT, "nocert", "neterror"); +}); + + +add_task(function* test_send_report_certerror() { + yield testSendReportAutomatically(URL_BAD_CERT, "badcert", "certerror"); + yield testSetAutomatic(URL_BAD_CERT, "badcert", "certerror"); +}); + +add_task(function* test_send_disabled() { + Services.prefs.setBoolPref(PREF_REPORT_ENABLED, false); + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true); + Services.prefs.setCharPref(PREF_REPORT_URL, "https://example.com/invalid"); + + // Check with enabled=false but automatic=true. + yield testSendReportDisabled(URL_NO_CERT, "neterror"); + yield testSendReportDisabled(URL_BAD_CERT, "certerror"); + + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false); + + // Check again with both prefs false. + yield testSendReportDisabled(URL_NO_CERT, "neterror"); + yield testSendReportDisabled(URL_BAD_CERT, "certerror"); + cleanup(); +}); + +function* testSendReportAutomatically(testURL, suffix, errorURISuffix) { + Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true); + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true); + Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix); + + // Add a tab and wait until it's loaded. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + + // Load the page and wait for the error report submission. + let promiseStatus = createReportResponseStatusPromise(URL_REPORTS + suffix); + browser.loadURI(testURL); + yield promiseErrorPageLoaded(browser); + + ok(!isErrorStatus(yield promiseStatus), + "SSL error report submitted successfully"); + + // Check that we loaded the right error page. + yield checkErrorPage(browser, errorURISuffix); + + // Cleanup. + gBrowser.removeTab(tab); + cleanup(); +} + +function* testSetAutomatic(testURL, suffix, errorURISuffix) { + Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true); + Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false); + Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix); + + // Add a tab and wait until it's loaded. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + + // Load the page. + browser.loadURI(testURL); + yield promiseErrorPageLoaded(browser); + + // Check that we loaded the right error page. + yield checkErrorPage(browser, errorURISuffix); + + let statusPromise = createReportResponseStatusPromise(URL_REPORTS + suffix); + + // Click the checkbox, enable automatic error reports. + yield ContentTask.spawn(browser, null, function* () { + content.document.getElementById("automaticallyReportInFuture").click(); + }); + + // Wait for the error report submission. + yield statusPromise; + + let isAutomaticReportingEnabled = () => + Services.prefs.getBoolPref(PREF_REPORT_AUTOMATIC); + + // Check that the pref was flipped. + ok(isAutomaticReportingEnabled(), "automatic SSL report submission enabled"); + + // Disable automatic error reports. + yield ContentTask.spawn(browser, null, function* () { + content.document.getElementById("automaticallyReportInFuture").click(); + }); + + // Check that the pref was flipped. + ok(!isAutomaticReportingEnabled(), "automatic SSL report submission disabled"); + + // Cleanup. + gBrowser.removeTab(tab); + cleanup(); +} + +function* testSendReportDisabled(testURL, errorURISuffix) { + // Add a tab and wait until it's loaded. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + + // Load the page. + browser.loadURI(testURL); + yield promiseErrorPageLoaded(browser); + + // Check that we loaded the right error page. + yield checkErrorPage(browser, errorURISuffix); + + // Check that the error reporting section is hidden. + yield ContentTask.spawn(browser, null, function* () { + let section = content.document.getElementById("certificateErrorReporting"); + Assert.equal(content.getComputedStyle(section).display, "none", + "error reporting section should be hidden"); + }); + + // Cleanup. + gBrowser.removeTab(tab); +} + +function isErrorStatus(status) { + return status < 200 || status >= 300; +} + +// use the observer service to see when a report is sent +function createReportResponseStatusPromise(expectedURI) { + return new Promise(resolve => { + let observer = (subject, topic, data) => { + subject.QueryInterface(Ci.nsIHttpChannel); + let requestURI = subject.URI.spec; + if (requestURI == expectedURI) { + Services.obs.removeObserver(observer, "http-on-examine-response"); + resolve(subject.responseStatus); + } + }; + Services.obs.addObserver(observer, "http-on-examine-response", false); + }); +} + +function checkErrorPage(browser, suffix) { + return ContentTask.spawn(browser, { suffix }, function* (args) { + let uri = content.document.documentURI; + Assert.ok(uri.startsWith(`about:${args.suffix}`), "correct error page loaded"); + }); +} diff --git a/browser/base/content/test/general/browser_star_hsts.js b/browser/base/content/test/general/browser_star_hsts.js new file mode 100644 index 000000000..c52e563bc --- /dev/null +++ b/browser/base/content/test/general/browser_star_hsts.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var secureURL = "https://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs"; +var unsecureURL = "http://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs"; + +add_task(function* test_star_redirect() { + registerCleanupFunction(function() { + // Ensure to remove example.com from the HSTS list. + let sss = Cc["@mozilla.org/ssservice;1"] + .getService(Ci.nsISiteSecurityService); + sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, + NetUtil.newURI("http://example.com/"), 0); + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + gBrowser.removeCurrentTab(); + }); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + // This will add the page to the HSTS cache. + yield promiseTabLoadEvent(tab, secureURL, secureURL); + // This should transparently be redirected to the secure page. + yield promiseTabLoadEvent(tab, unsecureURL, secureURL); + + yield promiseStarState(BookmarkingUI.STATUS_UNSTARRED); + + let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI); + BookmarkingUI.star.click(); + // This resolves on the next tick, so the star should have already been + // updated at that point. + yield promiseBookmark; + + is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred"); +}); + +/** + * Waits for the star to reflect the expected state. + */ +function promiseStarState(aValue) { + let deferred = Promise.defer(); + let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED + : BookmarkingUI.STATUS_UNSTARRED; + (function checkState() { + if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING || + BookmarkingUI.status != expectedStatus) { + info("Waiting for star button change."); + setTimeout(checkState, 1000); + } else { + deferred.resolve(); + } + })(); + return deferred.promise; +} + +/** + * Starts a load in an existing tab and waits for it to finish (via some event). + * + * @param aTab + * The tab to load into. + * @param aUrl + * The url to load. + * @param [optional] aFinalURL + * The url to wait for, same as aURL if not defined. + * @return {Promise} resolved when the event is handled. + */ +function promiseTabLoadEvent(aTab, aURL, aFinalURL) +{ + if (!aFinalURL) + aFinalURL = aURL; + let deferred = Promise.defer(); + info("Wait for load tab event"); + aTab.linkedBrowser.addEventListener("load", function load(event) { + if (event.originalTarget != aTab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + event.target.location.href != aFinalURL) { + info("skipping spurious load event"); + return; + } + aTab.linkedBrowser.removeEventListener("load", load, true); + info("Tab load event received"); + deferred.resolve(); + }, true, true); + aTab.linkedBrowser.loadURI(aURL); + return deferred.promise; +} diff --git a/browser/base/content/test/general/browser_star_hsts.sjs b/browser/base/content/test/general/browser_star_hsts.sjs new file mode 100644 index 000000000..10c7aae12 --- /dev/null +++ b/browser/base/content/test/general/browser_star_hsts.sjs @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function handleRequest(request, response) +{ + let page = "<!DOCTYPE html><html><body><p>HSTS page</p></body></html>"; + response.setStatusLine(request.httpVersion, "200", "OK"); + response.setHeader("Strict-Transport-Security", "max-age=60"); + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Content-Length", page.length + "", false); + response.write(page); +} diff --git a/browser/base/content/test/general/browser_subframe_favicons_not_used.js b/browser/base/content/test/general/browser_subframe_favicons_not_used.js new file mode 100644 index 000000000..7efe78d9b --- /dev/null +++ b/browser/base/content/test/general/browser_subframe_favicons_not_used.js @@ -0,0 +1,20 @@ +/* Make sure <link rel="..."> isn't respected in sub-frames. */ + +function test() { + waitForExplicitFinish(); + + let testPath = getRootDirectory(gTestPath); + + let tab = gBrowser.addTab(testPath + "file_bug970276_popup1.html"); + + tab.linkedBrowser.addEventListener("load", function() { + tab.linkedBrowser.removeEventListener("load", arguments.callee, true); + + let expectedIcon = testPath + "file_bug970276_favicon1.ico"; + is(gBrowser.getIcon(tab), expectedIcon, "Correct icon."); + + gBrowser.removeTab(tab); + + finish(); + }, true); +} diff --git a/browser/base/content/test/general/browser_syncui.js b/browser/base/content/test/general/browser_syncui.js new file mode 100644 index 000000000..daf0fa497 --- /dev/null +++ b/browser/base/content/test/general/browser_syncui.js @@ -0,0 +1,205 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var {Log} = Cu.import("resource://gre/modules/Log.jsm", {}); +var {Weave} = Cu.import("resource://services-sync/main.js", {}); + +var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"] + .getService(Ci.nsIStringBundleService) + .createBundle("chrome://weave/locale/services/sync.properties"); + +// ensure test output sees log messages. +Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender()); + +// Send the specified sync-related notification and return a promise that +// resolves once gSyncUI._promiseUpateUI is complete and the UI is ready to check. +function notifyAndPromiseUIUpdated(topic) { + return new Promise(resolve => { + // Instrument gSyncUI so we know when the update is complete. + let oldPromiseUpdateUI = gSyncUI._promiseUpdateUI.bind(gSyncUI); + gSyncUI._promiseUpdateUI = function() { + return oldPromiseUpdateUI().then(() => { + // Restore our override. + gSyncUI._promiseUpdateUI = oldPromiseUpdateUI; + // Resolve the promise so the caller knows the update is done. + resolve(); + }); + }; + // Now send the notification. + Services.obs.notifyObservers(null, topic, null); + }); +} + +// Sync manages 3 broadcasters so the menus correctly reflect the Sync state. +// Only one of these 3 should ever be visible - pass the ID of the broadcaster +// you expect to be visible and it will check it's the only one that is. +function checkBroadcasterVisible(broadcasterId) { + let all = ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"]; + Assert.ok(all.indexOf(broadcasterId) >= 0, "valid id"); + for (let check of all) { + let eltHidden = document.getElementById(check).hidden; + Assert.equal(eltHidden, check == broadcasterId ? false : true, check); + } +} + +function promiseObserver(topic) { + return new Promise(resolve => { + let obs = (aSubject, aTopic, aData) => { + Services.obs.removeObserver(obs, aTopic); + resolve(aSubject); + } + Services.obs.addObserver(obs, topic, false); + }); +} + +function checkButtonTooltips(stringPrefix) { + for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) { + let text = document.getElementById(butId).getAttribute("tooltiptext"); + let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"` + Assert.ok(text.startsWith(stringPrefix), desc); + } +} + +add_task(function* prepare() { + // add the Sync button to the toolbar so we can get it! + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_NAVBAR); + registerCleanupFunction(() => { + CustomizableUI.removeWidgetFromArea("sync-button"); + }); + + let xps = Components.classes["@mozilla.org/weave/service;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + yield xps.whenLoaded(); + // Put Sync and the UI into a known state. + Weave.Status.login = Weave.LOGIN_FAILED_NO_USERNAME; + yield notifyAndPromiseUIUpdated("weave:service:login:error"); + + checkBroadcasterVisible("sync-setup-state"); + checkButtonTooltips("Sign In To Sync"); + // mock out the "_needsSetup()" function so we don't short-circuit. + let oldNeedsSetup = window.gSyncUI._needsSetup; + window.gSyncUI._needsSetup = () => Promise.resolve(false); + registerCleanupFunction(() => { + window.gSyncUI._needsSetup = oldNeedsSetup; + // and an observer to set the state back to what it should be now we've + // restored the stub. + Services.obs.notifyObservers(null, "weave:service:login:finish", null); + }); + // and a notification to have the state change away from "needs setup" + yield notifyAndPromiseUIUpdated("weave:service:login:finish"); + checkBroadcasterVisible("sync-syncnow-state"); + // open the sync-button panel so we can check elements in that. + document.getElementById("sync-button").click(); +}); + +add_task(function* testSyncNeedsVerification() { + // mock out the "_needsVerification()" function + let oldNeedsVerification = window.gSyncUI._needsVerification; + window.gSyncUI._needsVerification = () => true; + try { + // a notification for the state change + yield notifyAndPromiseUIUpdated("weave:service:login:finish"); + checkButtonTooltips("Verify"); + } finally { + window.gSyncUI._needsVerification = oldNeedsVerification; + } +}); + + +add_task(function* testSyncLoginError() { + checkBroadcasterVisible("sync-syncnow-state"); + + // Pretend we are in a "login failed" error state + Weave.Status.sync = Weave.LOGIN_FAILED; + Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED; + yield notifyAndPromiseUIUpdated("weave:ui:sync:error"); + + // But the menu *should* reflect the login error. + checkBroadcasterVisible("sync-reauth-state"); + // The tooltips for the buttons should also reflect it. + checkButtonTooltips("Reconnect"); + + // Now pretend we just had a successful login - the error notification should go away. + Weave.Status.sync = Weave.STATUS_OK; + Weave.Status.login = Weave.LOGIN_SUCCEEDED; + yield notifyAndPromiseUIUpdated("weave:service:login:start"); + yield notifyAndPromiseUIUpdated("weave:service:login:finish"); + // The menus should be back to "all good" + checkBroadcasterVisible("sync-syncnow-state"); +}); + +function checkButtonsStatus(shouldBeActive) { + for (let eid of [ + "sync-status", // the broadcaster itself. + "sync-button", // the main sync button which observes the broadcaster + "PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it. + ]) { + let elt = document.getElementById(eid); + if (shouldBeActive) { + Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`); + } else { + Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`); + } + } +} + +function* testButtonActions(startNotification, endNotification, expectActive = true) { + checkButtonsStatus(false); + // pretend a sync is starting. + yield notifyAndPromiseUIUpdated(startNotification); + checkButtonsStatus(expectActive); + // and has stopped + yield notifyAndPromiseUIUpdated(endNotification); + checkButtonsStatus(false); +} + +function *doTestButtonActivities() { + // logins do not "activate" the spinner/button as they may block and make + // the UI look like Sync is never completing. + yield testButtonActions("weave:service:login:start", "weave:service:login:finish", false); + yield testButtonActions("weave:service:login:start", "weave:service:login:error", false); + + // But notifications for Sync itself should activate it. + yield testButtonActions("weave:service:sync:start", "weave:service:sync:finish"); + yield testButtonActions("weave:service:sync:start", "weave:service:sync:error"); + + // and ensure the counters correctly handle multiple in-flight syncs + yield notifyAndPromiseUIUpdated("weave:service:sync:start"); + checkButtonsStatus(true); + // sync stops. + yield notifyAndPromiseUIUpdated("weave:service:sync:finish"); + // Button should not be active. + checkButtonsStatus(false); +} + +add_task(function* testButtonActivitiesInNavBar() { + // check the button's functionality while the button is in the NavBar - which + // it already is. + yield doTestButtonActivities(); +}); + +add_task(function* testFormatLastSyncDateNow() { + let now = new Date(); + let nowString = gSyncUI.formatLastSyncDate(now); + Assert.equal(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: 'long', hour: 'numeric', minute: 'numeric'})); +}); + +add_task(function* testFormatLastSyncDateMonthAgo() { + let monthAgo = new Date(); + monthAgo.setMonth(monthAgo.getMonth() - 1); + let monthAgoString = gSyncUI.formatLastSyncDate(monthAgo); + Assert.equal(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: 'long', day: 'numeric'})); +}); + +add_task(function* testButtonActivitiesInPanel() { + // check the button's functionality while the button is in the panel - it's + // currently in the NavBar - move it to the panel and open it. + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + yield PanelUI.show(); + try { + yield doTestButtonActivities(); + } finally { + PanelUI.hide(); + } +}); diff --git a/browser/base/content/test/general/browser_tabDrop.js b/browser/base/content/test/general/browser_tabDrop.js new file mode 100644 index 000000000..fd743e6dc --- /dev/null +++ b/browser/base/content/test/general/browser_tabDrop.js @@ -0,0 +1,103 @@ +registerCleanupFunction(function* cleanup() { + while (gBrowser.tabs.length > 1) { + yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + } + Services.search.currentEngine = originalEngine; + let engine = Services.search.getEngineByName("MozSearch"); + Services.search.removeEngine(engine); +}); + +let originalEngine; +add_task(function* test_setup() { + // Stop search-engine loads from hitting the network + Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", + "http://example.com/?q={searchTerms}"); + let engine = Services.search.getEngineByName("MozSearch"); + originalEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; +}); + +add_task(function*() { yield dropText("mochi.test/first", 1); }); +add_task(function*() { yield dropText("javascript:'bad'"); }); +add_task(function*() { yield dropText("jAvascript:'bad'"); }); +add_task(function*() { yield dropText("search this", 1); }); +add_task(function*() { yield dropText("mochi.test/second", 1); }); +add_task(function*() { yield dropText("data:text/html,bad"); }); +add_task(function*() { yield dropText("mochi.test/third", 1); }); + +// Single text/plain item, with multiple links. +add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); }); +add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 0); }); +add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 0); }); + +// Multiple text/plain items, with single and multiple links. +add_task(function*() { + yield drop([[{type: "text/plain", + data: "mochi.test/5"}], + [{type: "text/plain", + data: "mochi.test/6\nmochi.test/7"}]], 3); +}); + +// Single text/x-moz-url item, with multiple links. +// "text/x-moz-url" has titles in even-numbered lines. +add_task(function*() { + yield drop([[{type: "text/x-moz-url", + data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2); +}); + +// Single item with multiple types. +add_task(function*() { + yield drop([[{type: "text/plain", + data: "mochi.test/10"}, + {type: "text/x-moz-url", + data: "mochi.test/11\nTITLE11"}]], 1); +}); + +function dropText(text, expectedTabOpenCount=0) { + return drop([[{type: "text/plain", data: text}]], expectedTabOpenCount); +} + +function* drop(dragData, expectedTabOpenCount=0) { + let dragDataString = JSON.stringify(dragData); + info(`Starting test for datagData:${dragDataString}; expectedTabOpenCount:${expectedTabOpenCount}`); + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop"); + let actualTabOpenCount = 0; + let openedTabs = []; + let checkCount = function(event) { + openedTabs.push(event.target); + actualTabOpenCount++; + return actualTabOpenCount == expectedTabOpenCount; + }; + let awaitTabOpen = expectedTabOpenCount && BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", false, checkCount); + // A drop type of "link" onto an existing tab would normally trigger a + // load in that same tab, but tabbrowser code in _getDragTargetTab treats + // drops on the outer edges of a tab differently (loading a new tab + // instead). Make events created by synthesizeDrop have all of their + // coordinates set to 0 (screenX/screenY), so they're treated as drops + // on the outer edge of the tab, thus they open new tabs. + var event = { + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, + }; + EventUtils.synthesizeDrop(gBrowser.selectedTab, gBrowser.selectedTab, dragData, "link", window, undefined, event); + let tabsOpened = false; + if (awaitTabOpen) { + yield awaitTabOpen; + info("Got TabOpen event"); + tabsOpened = true; + for (let tab of openedTabs) { + yield BrowserTestUtils.removeTab(tab); + } + } + is(tabsOpened, !!expectedTabOpenCount, `Tabs for ${dragDataString} should only open if any of dropped items are valid`); + + yield awaitDrop; + ok(true, "Got drop event"); +} diff --git a/browser/base/content/test/general/browser_tabReorder.js b/browser/base/content/test/general/browser_tabReorder.js new file mode 100644 index 000000000..9e0503e95 --- /dev/null +++ b/browser/base/content/test/general/browser_tabReorder.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + let initialTabsLength = gBrowser.tabs.length; + + let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true}); + let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true}); + let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true}); + registerCleanupFunction(function () { + while (gBrowser.tabs.length > initialTabsLength) { + gBrowser.removeTab(gBrowser.tabs[initialTabsLength]); + } + }); + + is(gBrowser.tabs.length, initialTabsLength + 3, "new tabs are opened"); + is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct"); + is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct"); + is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct"); + + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + function dragAndDrop(tab1, tab2, copy) { + let rect = tab2.getBoundingClientRect(); + let event = { + ctrlKey: copy, + altKey: copy, + clientX: rect.left + rect.width / 2 + 10, + clientY: rect.top + rect.height / 2, + }; + + EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event); + } + + dragAndDrop(newTab1, newTab2, false); + is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there"); + is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped"); + is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped"); + is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place"); + + dragAndDrop(newTab2, newTab1, true); + is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated"); + is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place"); + is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place"); + is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3"); +} diff --git a/browser/base/content/test/general/browser_tab_close_dependent_window.js b/browser/base/content/test/general/browser_tab_close_dependent_window.js new file mode 100644 index 000000000..ab8a960ac --- /dev/null +++ b/browser/base/content/test/general/browser_tab_close_dependent_window.js @@ -0,0 +1,24 @@ +"use strict"; + +add_task(function* closing_tab_with_dependents_should_close_window() { + info("Opening window"); + let win = yield BrowserTestUtils.openNewBrowserWindow(); + + info("Opening tab with data URI"); + let tab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, `data:text/html,<html%20onclick="W=window.open()"><body%20onbeforeunload="W.close()">`); + info("Closing original tab in this window."); + yield BrowserTestUtils.removeTab(win.gBrowser.tabs[0]); + info("Clicking into the window"); + let depTabOpened = BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, "TabOpen"); + yield BrowserTestUtils.synthesizeMouse("html", 0, 0, {}, tab.linkedBrowser); + + let openedTab = (yield depTabOpened).target; + info("Got opened tab"); + + let windowClosedPromise = BrowserTestUtils.windowClosed(win); + yield BrowserTestUtils.removeTab(tab); + is(Cu.isDeadWrapper(openedTab) || openedTab.linkedBrowser == null, true, "Opened tab should also have closed"); + info("If we timeout now, the window failed to close - that shouldn't happen!"); + yield windowClosedPromise; +}); + diff --git a/browser/base/content/test/general/browser_tab_detach_restore.js b/browser/base/content/test/general/browser_tab_detach_restore.js new file mode 100644 index 000000000..d482edc26 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_detach_restore.js @@ -0,0 +1,34 @@ +"use strict"; + +const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + +add_task(function*() { + let uri = "http://example.com/browser/browser/base/content/test/general/dummy_page.html"; + + // Clear out the closed windows set to start + while (SessionStore.getClosedWindowCount() > 0) + SessionStore.forgetClosedWindow(0); + + let tab = gBrowser.addTab(); + tab.linkedBrowser.loadURI(uri); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield TabStateFlusher.flush(tab.linkedBrowser); + + let key = tab.linkedBrowser.permanentKey; + let win = gBrowser.replaceTabWithWindow(tab); + yield new Promise(resolve => whenDelayedStartupFinished(win, resolve)); + + is(win.gBrowser.selectedBrowser.permanentKey, key, "Should have properly copied the permanentKey"); + yield BrowserTestUtils.closeWindow(win); + + is(SessionStore.getClosedWindowCount(), 1, "Should have restore data for the closed window"); + + win = SessionStore.undoCloseWindow(0); + yield BrowserTestUtils.waitForEvent(win, "load"); + yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored"); + + is(win.gBrowser.tabs.length, 1, "Should have restored one tab"); + is(win.gBrowser.selectedBrowser.currentURI.spec, uri, "Should have restored the right page"); + + yield promiseWindowClosed(win); +}); diff --git a/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js new file mode 100644 index 000000000..a8fc34083 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +requestLongerTimeout(2); + +const EVENTUTILS_URL = "chrome://mochikit/content/tests/SimpleTest/EventUtils.js"; +var EventUtils = {}; + +Services.scriptloader.loadSubScript(EVENTUTILS_URL, EventUtils); + +/** + * Tests that tabs from Private Browsing windows cannot be dragged + * into non-private windows, and vice-versa. + */ +add_task(function* test_dragging_private_windows() { + let normalWin = yield BrowserTestUtils.openNewBrowserWindow(); + let privateWin = + yield BrowserTestUtils.openNewBrowserWindow({private: true}); + + let normalTab = + yield BrowserTestUtils.openNewForegroundTab(normalWin.gBrowser); + let privateTab = + yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser); + + let effect = EventUtils.synthesizeDrop(normalTab, privateTab, + [[{type: TAB_DROP_TYPE, data: normalTab}]], + null, normalWin, privateWin); + is(effect, "none", "Should not be able to drag a normal tab to a private window"); + + effect = EventUtils.synthesizeDrop(privateTab, normalTab, + [[{type: TAB_DROP_TYPE, data: privateTab}]], + null, privateWin, normalWin); + is(effect, "none", "Should not be able to drag a private tab to a normal window"); + + normalWin.gBrowser.swapBrowsersAndCloseOther(normalTab, privateTab); + is(normalWin.gBrowser.tabs.length, 2, + "Prevent moving a normal tab to a private tabbrowser"); + is(privateWin.gBrowser.tabs.length, 2, + "Prevent accepting a normal tab in a private tabbrowser"); + + privateWin.gBrowser.swapBrowsersAndCloseOther(privateTab, normalTab); + is(privateWin.gBrowser.tabs.length, 2, + "Prevent moving a private tab to a normal tabbrowser"); + is(normalWin.gBrowser.tabs.length, 2, + "Prevent accepting a private tab in a normal tabbrowser"); + + yield BrowserTestUtils.closeWindow(normalWin); + yield BrowserTestUtils.closeWindow(privateWin); +}); + +/** + * Tests that tabs from e10s windows cannot be dragged into non-e10s + * windows, and vice-versa. + */ +add_task(function* test_dragging_e10s_windows() { + if (!gMultiProcessBrowser) { + return; + } + + let remoteWin = yield BrowserTestUtils.openNewBrowserWindow({remote: true}); + let nonRemoteWin = yield BrowserTestUtils.openNewBrowserWindow({remote: false}); + + let remoteTab = + yield BrowserTestUtils.openNewForegroundTab(remoteWin.gBrowser); + let nonRemoteTab = + yield BrowserTestUtils.openNewForegroundTab(nonRemoteWin.gBrowser); + + let effect = EventUtils.synthesizeDrop(remoteTab, nonRemoteTab, + [[{type: TAB_DROP_TYPE, data: remoteTab}]], + null, remoteWin, nonRemoteWin); + is(effect, "none", "Should not be able to drag a remote tab to a non-e10s window"); + + effect = EventUtils.synthesizeDrop(nonRemoteTab, remoteTab, + [[{type: TAB_DROP_TYPE, data: nonRemoteTab}]], + null, nonRemoteWin, remoteWin); + is(effect, "none", "Should not be able to drag a non-remote tab to an e10s window"); + + remoteWin.gBrowser.swapBrowsersAndCloseOther(remoteTab, nonRemoteTab); + is(remoteWin.gBrowser.tabs.length, 2, + "Prevent moving a normal tab to a private tabbrowser"); + is(nonRemoteWin.gBrowser.tabs.length, 2, + "Prevent accepting a normal tab in a private tabbrowser"); + + nonRemoteWin.gBrowser.swapBrowsersAndCloseOther(nonRemoteTab, remoteTab); + is(nonRemoteWin.gBrowser.tabs.length, 2, + "Prevent moving a private tab to a normal tabbrowser"); + is(remoteWin.gBrowser.tabs.length, 2, + "Prevent accepting a private tab in a normal tabbrowser"); + + yield BrowserTestUtils.closeWindow(remoteWin); + yield BrowserTestUtils.closeWindow(nonRemoteWin); +}); + +/** + * Tests that remoteness-blacklisted tabs from e10s windows can + * be dragged between e10s windows. + */ +add_task(function* test_dragging_blacklisted() { + if (!gMultiProcessBrowser) { + return; + } + + let remoteWin1 = yield BrowserTestUtils.openNewBrowserWindow({remote: true}); + remoteWin1.gBrowser.myID = "remoteWin1"; + let remoteWin2 = yield BrowserTestUtils.openNewBrowserWindow({remote: true}); + remoteWin2.gBrowser.myID = "remoteWin2"; + + // Anything under chrome://mochitests/content/ will be blacklisted, and + // open in the parent process. + const BLACKLISTED_URL = getRootDirectory(gTestPath) + + "browser_tab_drag_drop_perwindow.js"; + let blacklistedTab = + yield BrowserTestUtils.openNewForegroundTab(remoteWin1.gBrowser, + BLACKLISTED_URL); + + ok(blacklistedTab.linkedBrowser, "Newly created tab should have a browser."); + + ok(!blacklistedTab.linkedBrowser.isRemoteBrowser, + `Expected a non-remote browser for URL: ${BLACKLISTED_URL}`); + + let otherTab = + yield BrowserTestUtils.openNewForegroundTab(remoteWin2.gBrowser); + + let effect = EventUtils.synthesizeDrop(blacklistedTab, otherTab, + [[{type: TAB_DROP_TYPE, data: blacklistedTab}]], + null, remoteWin1, remoteWin2); + is(effect, "move", "Should be able to drag the blacklisted tab."); + + // The synthesized drop should also do the work of swapping the + // browsers, so no need to call swapBrowsersAndCloseOther manually. + + is(remoteWin1.gBrowser.tabs.length, 1, + "Should have moved the blacklisted tab out of this window."); + is(remoteWin2.gBrowser.tabs.length, 3, + "Should have inserted the blacklisted tab into the other window."); + + // The currently selected tab in the second window should be the + // one we just dragged in. + let draggedBrowser = remoteWin2.gBrowser.selectedBrowser; + ok(!draggedBrowser.isRemoteBrowser, + "The browser we just dragged in should not be remote."); + + is(draggedBrowser.currentURI.spec, BLACKLISTED_URL, + `Expected the URL of the dragged in tab to be ${BLACKLISTED_URL}`); + + yield BrowserTestUtils.closeWindow(remoteWin1); + yield BrowserTestUtils.closeWindow(remoteWin2); +}); + + +/** + * Tests that tabs dragged between windows dispatch TabOpen and TabClose + * events with the appropriate adoption details. + */ +add_task(function* test_dragging_adoption_events() { + let win1 = yield BrowserTestUtils.openNewBrowserWindow(); + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + + let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser); + let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser); + + let awaitCloseEvent = BrowserTestUtils.waitForEvent(tab1, "TabClose"); + let awaitOpenEvent = BrowserTestUtils.waitForEvent(win2, "TabOpen"); + + let effect = EventUtils.synthesizeDrop(tab1, tab2, + [[{type: TAB_DROP_TYPE, data: tab1}]], + null, win1, win2); + is(effect, "move", "Tab should be moved from win1 to win2."); + + let closeEvent = yield awaitCloseEvent; + let openEvent = yield awaitOpenEvent; + + is(openEvent.detail.adoptedTab, tab1, "New tab adopted old tab"); + is(closeEvent.detail.adoptedBy, openEvent.target, "Old tab adopted by new tab"); + + yield BrowserTestUtils.closeWindow(win1); + yield BrowserTestUtils.closeWindow(win2); +}); + + +/** + * Tests that per-site zoom settings remain active after a tab is + * dragged between windows. + */ +add_task(function* test_dragging_zoom_handling() { + const ZOOM_FACTOR = 1.62; + + let win1 = yield BrowserTestUtils.openNewBrowserWindow(); + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + + let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser); + let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser, + "http://example.com/"); + + win2.FullZoom.setZoom(ZOOM_FACTOR); + FullZoomHelper.zoomTest(tab2, ZOOM_FACTOR, + "Original tab should have correct zoom factor"); + + let effect = EventUtils.synthesizeDrop(tab2, tab1, + [[{type: TAB_DROP_TYPE, data: tab2}]], + null, win2, win1); + is(effect, "move", "Tab should be moved from win2 to win1."); + + // Delay slightly to make sure we've finished executing any promise + // chains in the zoom code. + yield new Promise(resolve => setTimeout(resolve, 0)); + + FullZoomHelper.zoomTest(win1.gBrowser.selectedTab, ZOOM_FACTOR, + "Dragged tab should have correct zoom factor"); + + win1.FullZoom.reset(); + + yield BrowserTestUtils.closeWindow(win1); + yield BrowserTestUtils.closeWindow(win2); +}); diff --git a/browser/base/content/test/general/browser_tab_dragdrop.js b/browser/base/content/test/general/browser_tab_dragdrop.js new file mode 100644 index 000000000..cfe996e1e --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop.js @@ -0,0 +1,186 @@ +function swapTabsAndCloseOther(a, b) { + gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]); +} + +var getClicks = function(tab) { + return ContentTask.spawn(tab.linkedBrowser, {}, function() { + return content.wrappedJSObject.clicks; + }); +} + +var clickTest = Task.async(function*(tab) { + let clicks = yield getClicks(tab); + + yield ContentTask.spawn(tab.linkedBrowser, {}, function() { + let target = content.document.body; + let rect = target.getBoundingClientRect(); + let left = (rect.left + rect.right) / 2; + let top = (rect.top + rect.bottom) / 2; + + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + let newClicks = yield getClicks(tab); + is(newClicks, clicks + 1, "adding 1 more click on BODY"); +}); + +function loadURI(tab, url) { + tab.linkedBrowser.loadURI(url); + return BrowserTestUtils.browserLoaded(tab.linkedBrowser); +} + +// Creates a framescript which caches the current object value from the plugin +// in the page. checkObjectValue below verifies that the framescript is still +// active for the browser and that the cached value matches that from the plugin +// in the page which tells us the plugin hasn't been reinitialized. +function* cacheObjectValue(browser) { + yield ContentTask.spawn(browser, null, function*() { + let plugin = content.document.wrappedJSObject.body.firstChild; + info(`plugin is ${plugin}`); + let win = content.document.defaultView; + info(`win is ${win}`); + win.objectValue = plugin.getObjectValue(); + info(`got objectValue: ${win.objectValue}`); + win.checkObjectValueListener = () => { + let result; + let exception; + try { + result = plugin.checkObjectValue(win.objectValue); + } catch (e) { + exception = e.toString(); + } + info(`sending plugin.checkObjectValue(objectValue): ${result}`); + sendAsyncMessage("Test:CheckObjectValueResult", { + result, + exception + }); + }; + + addMessageListener("Test:CheckObjectValue", win.checkObjectValueListener); + }); +} + +// Note, can't run this via registerCleanupFunction because it needs the +// browser to still be alive and have a messageManager. +function* cleanupObjectValue(browser) { + info("entered cleanupObjectValue") + yield ContentTask.spawn(browser, null, function*() { + info("in cleanup function"); + let win = content.document.defaultView; + info(`about to delete objectValue: ${win.objectValue}`); + delete win.objectValue; + removeMessageListener("Test:CheckObjectValue", win.checkObjectValueListener); + info(`about to delete checkObjectValueListener: ${win.checkObjectValueListener}`); + delete win.checkObjectValueListener; + info(`deleted objectValue (${win.objectValue}) and checkObjectValueListener (${win.checkObjectValueListener})`); + }); + info("exiting cleanupObjectValue") +} + +// See the notes for cacheObjectValue above. +function checkObjectValue(browser) { + let mm = browser.messageManager; + + return new Promise((resolve, reject) => { + let listener = ({ data }) => { + mm.removeMessageListener("Test:CheckObjectValueResult", listener); + if (data.result === null) { + ok(false, "checkObjectValue threw an exception: " + data.exception); + reject(data.exception); + } else { + resolve(data.result); + } + }; + + mm.addMessageListener("Test:CheckObjectValueResult", listener); + mm.sendAsyncMessage("Test:CheckObjectValue"); + }); +} + +add_task(function*() { + let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>' + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED); + + // create a few tabs + let tabs = [ + gBrowser.tabs[0], + gBrowser.addTab("about:blank", {skipAnimation: true}), + gBrowser.addTab("about:blank", {skipAnimation: true}), + gBrowser.addTab("about:blank", {skipAnimation: true}), + gBrowser.addTab("about:blank", {skipAnimation: true}) + ]; + + // Initially 0 1 2 3 4 + yield loadURI(tabs[1], "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>"); + yield loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2"); + yield loadURI(tabs[3], "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>"); + yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed); + yield BrowserTestUtils.switchTab(gBrowser, tabs[3]); + + swapTabsAndCloseOther(2, 3); // now: 0 1 2 4 + is(gBrowser.tabs[1], tabs[1], "tab1"); + is(gBrowser.tabs[2], tabs[3], "tab3"); + is(gBrowser.tabs[3], tabs[4], "tab4"); + delete tabs[2]; + + info("about to cacheObjectValue") + yield cacheObjectValue(tabs[4].linkedBrowser); + info("just finished cacheObjectValue") + + swapTabsAndCloseOther(3, 2); // now: 0 1 4 + is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 2, + "The third tab should be selected"); + delete tabs[4]; + + + ok((yield checkObjectValue(gBrowser.tabs[2].linkedBrowser)), "same plugin instance"); + + is(gBrowser.tabs[1], tabs[1], "tab1"); + is(gBrowser.tabs[2], tabs[3], "tab4"); + + let clicks = yield getClicks(gBrowser.tabs[2]); + is(clicks, 0, "no click on BODY so far"); + yield clickTest(gBrowser.tabs[2]); + + swapTabsAndCloseOther(2, 1); // now: 0 4 + is(gBrowser.tabs[1], tabs[1], "tab1"); + delete tabs[3]; + + ok((yield checkObjectValue(gBrowser.tabs[1].linkedBrowser)), "same plugin instance"); + yield cleanupObjectValue(gBrowser.tabs[1].linkedBrowser); + + yield clickTest(gBrowser.tabs[1]); + + // Load a new document (about:blank) in tab4, then detach that tab into a new window. + // In the new window, navigate back to the original document and click on its <body>, + // verify that its onclick was called. + is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 1, + "The second tab should be selected"); + is(gBrowser.tabs[1], tabs[1], + "The second tab in gBrowser.tabs should be equal to the second tab in our array"); + is(gBrowser.selectedTab, tabs[1], + "The second tab in our array is the selected tab"); + yield loadURI(tabs[1], "about:blank"); + let key = tabs[1].linkedBrowser.permanentKey; + + let win = gBrowser.replaceTabWithWindow(tabs[1]); + yield new Promise(resolve => whenDelayedStartupFinished(win, resolve)); + delete tabs[1]; + + // Verify that the original window now only has the initial tab left in it. + is(gBrowser.tabs[0], tabs[0], "tab0"); + is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:blank", "tab0 uri"); + + let tab = win.gBrowser.tabs[0]; + is(tab.linkedBrowser.permanentKey, key, "Should have kept the key"); + + let awaitPageShow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow"); + win.gBrowser.goBack(); + yield awaitPageShow; + + yield clickTest(tab); + promiseWindowClosed(win); +}); diff --git a/browser/base/content/test/general/browser_tab_dragdrop2.js b/browser/base/content/test/general/browser_tab_dragdrop2.js new file mode 100644 index 000000000..2ab622d8b --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop2.js @@ -0,0 +1,57 @@ +"use strict"; + +const ROOT = getRootDirectory(gTestPath); +const URI = ROOT + "browser_tab_dragdrop2_frame1.xul"; + +// Load the test page (which runs some child popup tests) in a new window. +// After the tests were run, tear off the tab into a new window and run popup +// tests a second time. We don't care about tests results, exceptions and +// crashes will be caught. +add_task(function* () { + // Open a new window. + let args = "chrome,all,dialog=no"; + let win = window.openDialog(getBrowserURL(), "_blank", args, URI); + + // Wait until the tests were run. + yield promiseTestsDone(win); + ok(true, "tests succeeded"); + + // Create a second tab so that we can move the original one out. + win.gBrowser.addTab("about:blank", {skipAnimation: true}); + + // Tear off the original tab. + let browser = win.gBrowser.selectedBrowser; + let tabClosed = promiseWaitForEvent(browser, "pagehide", true); + let win2 = win.gBrowser.replaceTabWithWindow(win.gBrowser.tabs[0]); + + // Add a 'TestsDone' event listener to ensure that the docShells is properly + // swapped to the new window instead of the page being loaded again. If this + // works fine we should *NOT* see a TestsDone event. + let onTestsDone = () => ok(false, "shouldn't run tests when tearing off"); + win2.addEventListener("TestsDone", onTestsDone); + + // Wait until the original tab is gone and the new window is ready. + yield Promise.all([tabClosed, promiseDelayedStartupFinished(win2)]); + + // Remove the 'TestsDone' event listener as now + // we're kicking off a new test run manually. + win2.removeEventListener("TestsDone", onTestsDone); + + // Run tests once again. + let promise = promiseTestsDone(win2); + win2.content.test_panels(); + yield promise; + ok(true, "tests succeeded a second time"); + + // Cleanup. + yield promiseWindowClosed(win2); + yield promiseWindowClosed(win); +}); + +function promiseTestsDone(win) { + return promiseWaitForEvent(win, "TestsDone"); +} + +function promiseDelayedStartupFinished(win) { + return new Promise(resolve => whenDelayedStartupFinished(win, resolve)); +} diff --git a/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul new file mode 100644 index 000000000..d11709942 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul @@ -0,0 +1,169 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for panels + --> +<window title="Titlebar" width="200" height="200" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<tree id="tree" seltype="single" width="100" height="100"> + <treecols> + <treecol flex="1"/> + <treecol flex="1"/> + </treecols> + <treechildren id="treechildren"> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + </treechildren> +</tree> + + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var currentTest = null; + +var i, waitSteps; +var my_debug = false; +function test_panels() +{ + i = waitSteps = 0; + checkTreeCoords(); + + addEventListener("popupshown", popupShown, false); + addEventListener("popuphidden", nextTest, false); + return nextTest(); +} + +function nextTest() +{ + ok(true,"popuphidden " + i) + if (i == tests.length) { + let details = {bubbles: true, cancelable: false}; + document.dispatchEvent(new CustomEvent("TestsDone", details)); + return i; + } + + currentTest = tests[i]; + var panel = createPanel(currentTest.attrs); + SimpleTest.waitForFocus(() => currentTest.test(panel)); + return i; +} + +function popupShown(event) +{ + var panel = event.target; + if (waitSteps > 0 && navigator.platform.indexOf("Linux") >= 0 && + panel.boxObject.screenY == 210) { + waitSteps--; + setTimeout(popupShown, 10, event); + return; + } + ++i; + + currentTest.result(currentTest.testname + " ", panel); + panel.hidePopup(); +} + +function createPanel(attrs) +{ + var panel = document.createElement("panel"); + for (var a in attrs) { + panel.setAttribute(a, attrs[a]); + } + + var button = document.createElement("button"); + panel.appendChild(button); + button.label = "OK"; + button.width = 120; + button.height = 40; + button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;"); + panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;"); + return document.documentElement.appendChild(panel); +} + +function checkTreeCoords() +{ + var tree = $("tree"); + var treechildren = $("treechildren"); + tree.currentIndex = 0; + tree.treeBoxObject.scrollToRow(0); + synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { }); + + tree.treeBoxObject.scrollToRow(2); + synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { }); +} + +var tests = [ + { + testname: "normal panel", + attrs: { }, + test: function(panel) { + panel.openPopupAtScreen(200, 210); + }, + result: function(testname, panel) { + if (my_debug) alert(testname); + var panelrect = panel.getBoundingClientRect(); + } + }, + { + // only noautohide panels support titlebars, so one shouldn't be shown here + testname: "autohide panel with titlebar", + attrs: { titlebar: "normal" }, + test: function(panel) { + panel.openPopupAtScreen(200, 210); + }, + result: function(testname, panel) { + if (my_debug) alert(testname); + var panelrect = panel.getBoundingClientRect(); + } + }, + { + testname: "noautohide panel with titlebar", + attrs: { noautohide: true, titlebar: "normal" }, + test: function(panel) { + waitSteps = 25; + panel.openPopupAtScreen(200, 210); + }, + result: function(testname, panel) { + if (my_debug) alert(testname); + var panelrect = panel.getBoundingClientRect(); + + var gotMouseEvent = false; + function mouseMoved(event) + { + gotMouseEvent = true; + } + + panel.addEventListener("mousemove", mouseMoved, true); + synthesizeMouse(panel, 10, 10, { type: "mousemove" }); + panel.removeEventListener("mousemove", mouseMoved, true); + + var tree = $("tree"); + tree.currentIndex = 0; + panel.appendChild(tree); + checkTreeCoords(); + } + } +]; + +SimpleTest.waitForFocus(test_panels); + +]]> +</script> + +</window> diff --git a/browser/base/content/test/general/browser_tabbar_big_widgets.js b/browser/base/content/test/general/browser_tabbar_big_widgets.js new file mode 100644 index 000000000..7a4c45138 --- /dev/null +++ b/browser/base/content/test/general/browser_tabbar_big_widgets.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const kButtonId = "test-tabbar-size-with-large-buttons"; + +function test() { + registerCleanupFunction(cleanup); + let titlebar = document.getElementById("titlebar"); + let originalHeight = titlebar.getBoundingClientRect().height; + let button = document.createElement("toolbarbutton"); + button.id = kButtonId; + button.setAttribute("style", "min-height: 100px"); + gNavToolbox.palette.appendChild(button); + CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_TABSTRIP); + let currentHeight = titlebar.getBoundingClientRect().height; + ok(currentHeight > originalHeight, "Titlebar should have grown"); + CustomizableUI.removeWidgetFromArea(kButtonId); + currentHeight = titlebar.getBoundingClientRect().height; + is(currentHeight, originalHeight, "Titlebar should have gone back to its original size."); +} + +function cleanup() { + let btn = document.getElementById(kButtonId); + if (btn) { + btn.remove(); + } +} + diff --git a/browser/base/content/test/general/browser_tabfocus.js b/browser/base/content/test/general/browser_tabfocus.js new file mode 100644 index 000000000..4042421e8 --- /dev/null +++ b/browser/base/content/test/general/browser_tabfocus.js @@ -0,0 +1,565 @@ +/* + * This test checks that focus is adjusted properly when switching tabs. + */ + +var testPage1 = "<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>"; +var testPage2 = "<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>"; +var testPage3 = "<html id='html3'><body id='body3'><button id='button3'>Tab 3</button></body></html>"; + +const fm = Services.focus; + +function EventStore() { + this["main-window"] = []; + this["window1"] = []; + this["window2"] = []; +} + +EventStore.prototype = { + "push": function (event) { + if (event.indexOf("1") > -1) { + this["window1"].push(event); + } else if (event.indexOf("2") > -1) { + this["window2"].push(event); + } else { + this["main-window"].push(event); + } + } +} + +var tab1 = null; +var tab2 = null; +var browser1 = null; +var browser2 = null; +var _lastfocus; +var _lastfocuswindow = null; +var actualEvents = new EventStore(); +var expectedEvents = new EventStore(); +var currentTestName = ""; +var _expectedElement = null; +var _expectedWindow = null; + +var currentPromiseResolver = null; + +function* getFocusedElementForBrowser(browser, dontCheckExtraFocus = false) +{ + if (gMultiProcessBrowser) { + return new Promise((resolve, reject) => { + messageManager.addMessageListener("Browser:GetCurrentFocus", function getCurrentFocus(message) { + messageManager.removeMessageListener("Browser:GetCurrentFocus", getCurrentFocus); + resolve(message.data.details); + }); + + // The dontCheckExtraFocus flag is used to indicate not to check some + // additional focus related properties. This is needed as both URLs are + // loaded using the same child process and share focus managers. + browser.messageManager.sendAsyncMessage("Browser:GetFocusedElement", + { dontCheckExtraFocus : dontCheckExtraFocus }); + }); + } + var focusedWindow = {}; + var node = fm.getFocusedElementForWindow(browser.contentWindow, false, focusedWindow); + return "Focus is " + (node ? node.id : "<none>"); +} + +function focusInChild() +{ + var contentFM = Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager); + + function getWindowDocId(target) + { + return (String(target.location).indexOf("1") >= 0) ? "window1" : "window2"; + } + + function eventListener(event) { + var id; + if (event.target instanceof Components.interfaces.nsIDOMWindow) + id = getWindowDocId(event.originalTarget) + "-window"; + else if (event.target instanceof Components.interfaces.nsIDOMDocument) + id = getWindowDocId(event.originalTarget) + "-document"; + else + id = event.originalTarget.id; + sendSyncMessage("Browser:FocusChanged", { details : event.type + ": " + id }); + } + + addEventListener("focus", eventListener, true); + addEventListener("blur", eventListener, true); + + addMessageListener("Browser:ChangeFocus", function changeFocus(message) { + content.document.getElementById(message.data.id)[message.data.type](); + }); + + addMessageListener("Browser:GetFocusedElement", function getFocusedElement(message) { + var focusedWindow = {}; + var node = contentFM.getFocusedElementForWindow(content, false, focusedWindow); + var details = "Focus is " + (node ? node.id : "<none>"); + + /* Check focus manager properties. Add an error onto the string if they are + not what is expected which will cause matching to fail in the parent process. */ + let doc = content.document; + if (!message.data.dontCheckExtraFocus) { + if (contentFM.focusedElement != node) { + details += "<ERROR: focusedElement doesn't match>"; + } + if (contentFM.focusedWindow && contentFM.focusedWindow != content) { + details += "<ERROR: focusedWindow doesn't match>"; + } + if ((contentFM.focusedWindow == content) != doc.hasFocus()) { + details += "<ERROR: child hasFocus() is not correct>"; + } + if ((contentFM.focusedElement && doc.activeElement != contentFM.focusedElement) || + (!contentFM.focusedElement && doc.activeElement != doc.body)) { + details += "<ERROR: child activeElement is not correct>"; + } + } + + sendSyncMessage("Browser:GetCurrentFocus", { details : details }); + }); +} + +function focusElementInChild(elementid, type) +{ + let browser = (elementid.indexOf("1") >= 0) ? browser1 : browser2; + if (gMultiProcessBrowser) { + browser.messageManager.sendAsyncMessage("Browser:ChangeFocus", + { id: elementid, type: type }); + } + else { + browser.contentDocument.getElementById(elementid)[type](); + } +} + +add_task(function*() { + tab1 = gBrowser.addTab(); + browser1 = gBrowser.getBrowserForTab(tab1); + + tab2 = gBrowser.addTab(); + browser2 = gBrowser.getBrowserForTab(tab2); + + yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage1)); + yield promiseTabLoadEvent(tab2, "data:text/html," + escape(testPage2)); + + var childFocusScript = "data:,(" + focusInChild.toString() + ")();"; + browser1.messageManager.loadFrameScript(childFocusScript, true); + browser2.messageManager.loadFrameScript(childFocusScript, true); + + gURLBar.focus(); + yield SimpleTest.promiseFocus(); + + if (gMultiProcessBrowser) { + messageManager.addMessageListener("Browser:FocusChanged", message => { + actualEvents.push(message.data.details); + compareFocusResults(); + }); + } + + _lastfocus = "urlbar"; + _lastfocuswindow = "main-window"; + + window.addEventListener("focus", _browser_tabfocus_test_eventOccured, true); + window.addEventListener("blur", _browser_tabfocus_test_eventOccured, true); + + // make sure that the focus initially starts out blank + var focusedWindow = {}; + + let focused = yield getFocusedElementForBrowser(browser1); + is(focused, "Focus is <none>", "initial focus in tab 1"); + + focused = yield getFocusedElementForBrowser(browser2); + is(focused, "Focus is <none>", "initial focus in tab 2"); + + is(document.activeElement, gURLBar.inputField, "focus after loading two tabs"); + + yield* expectFocusShiftAfterTabSwitch(tab2, "window2", null, true, + "after tab change, focus in new tab"); + + focused = yield getFocusedElementForBrowser(browser2); + is(focused, "Focus is <none>", "focusedElement after tab change, focus in new tab"); + + // switching tabs when nothing in the new tab is focused + // should focus the browser + yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true, + "after tab change, focus in original tab"); + + focused = yield getFocusedElementForBrowser(browser1); + is(focused, "Focus is <none>", "focusedElement after tab change, focus in original tab"); + + // focusing a button in the current tab should focus it + yield expectFocusShift(() => focusElementInChild("button1", "focus"), + "window1", "button1", true, + "after button focused"); + + focused = yield getFocusedElementForBrowser(browser1); + is(focused, "Focus is button1", "focusedElement in first browser after button focused"); + + // focusing a button in a background tab should not change the actual + // focus, but should set the focus that would be in that background tab to + // that button. + yield expectFocusShift(() => focusElementInChild("button2", "focus"), + "window1", "button1", false, + "after button focus in unfocused tab"); + + focused = yield getFocusedElementForBrowser(browser1, false); + is(focused, "Focus is button1", "focusedElement in first browser after button focus in unfocused tab"); + focused = yield getFocusedElementForBrowser(browser2, true); + is(focused, "Focus is button2", "focusedElement in second browser after button focus in unfocused tab"); + + // switching tabs should now make the button in the other tab focused + yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true, + "after tab change with button focused"); + + // blurring an element in a background tab should not change the active + // focus, but should clear the focus in that tab. + yield expectFocusShift(() => focusElementInChild("button1", "blur"), + "window2", "button2", false, + "focusedWindow after blur in unfocused tab"); + + focused = yield getFocusedElementForBrowser(browser1, true); + is(focused, "Focus is <none>", "focusedElement in first browser after focus in unfocused tab"); + focused = yield getFocusedElementForBrowser(browser2, false); + is(focused, "Focus is button2", "focusedElement in second browser after focus in unfocused tab"); + + // When focus is in the tab bar, it should be retained there + yield expectFocusShift(() => gBrowser.selectedTab.focus(), + "main-window", "tab2", true, + "focusing tab element"); + yield* expectFocusShiftAfterTabSwitch(tab1, "main-window", "tab1", true, + "tab change when selected tab element was focused"); + + let switchWaiter; + if (gMultiProcessBrowser) { + switchWaiter = new Promise((resolve, reject) => { + gBrowser.addEventListener("TabSwitchDone", function listener() { + gBrowser.removeEventListener("TabSwitchDone", listener); + executeSoon(resolve); + }); + }); + } + + yield* expectFocusShiftAfterTabSwitch(tab2, "main-window", "tab2", true, + "another tab change when selected tab element was focused"); + + // When this a remote browser, wait for the paint on the second browser so that + // any post tab-switching stuff has time to complete before blurring the tab. + // Otherwise, the _adjustFocusAfterTabSwitch in tabbrowser gets confused and + // isn't sure what tab is really focused. + if (gMultiProcessBrowser) { + yield switchWaiter; + } + + yield expectFocusShift(() => gBrowser.selectedTab.blur(), + "main-window", null, true, + "blurring tab element"); + + // focusing the url field should switch active focus away from the browser but + // not clear what would be the focus in the browser + focusElementInChild("button1", "focus"); + + yield expectFocusShift(() => gURLBar.focus(), + "main-window", "urlbar", true, + "focusedWindow after url field focused"); + focused = yield getFocusedElementForBrowser(browser1, true); + is(focused, "Focus is button1", "focusedElement after url field focused, first browser"); + focused = yield getFocusedElementForBrowser(browser2, true); + is(focused, "Focus is button2", "focusedElement after url field focused, second browser"); + + yield expectFocusShift(() => gURLBar.blur(), + "main-window", null, true, + "blurring url field"); + + // when a chrome element is focused, switching tabs to a tab with a button + // with the current focus should focus the button + yield* expectFocusShiftAfterTabSwitch(tab1, "window1", "button1", true, + "after tab change, focus in url field, button focused in new tab"); + + focused = yield getFocusedElementForBrowser(browser1, false); + is(focused, "Focus is button1", "after switch tab, focus in unfocused tab, first browser"); + focused = yield getFocusedElementForBrowser(browser2, true); + is(focused, "Focus is button2", "after switch tab, focus in unfocused tab, second browser"); + + // blurring an element in the current tab should clear the active focus + yield expectFocusShift(() => focusElementInChild("button1", "blur"), + "window1", null, true, + "after blur in focused tab"); + + focused = yield getFocusedElementForBrowser(browser1, false); + is(focused, "Focus is <none>", "focusedWindow after blur in focused tab, child"); + focusedWindow = {}; + is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in focused tab, parent"); + + // blurring an non-focused url field should have no effect + yield expectFocusShift(() => gURLBar.blur(), + "window1", null, false, + "after blur in unfocused url field"); + + focusedWindow = {}; + is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in unfocused url field"); + + // switch focus to a tab with a currently focused element + yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true, + "after switch from unfocused to focused tab"); + focused = yield getFocusedElementForBrowser(browser2, true); + is(focused, "Focus is button2", "focusedElement after switch from unfocused to focused tab"); + + // clearing focus on the chrome window should switch the focus to the + // chrome window + yield expectFocusShift(() => fm.clearFocus(window), + "main-window", null, true, + "after switch to chrome with no focused element"); + + focusedWindow = {}; + is(fm.getFocusedElementForWindow(window, false, focusedWindow), null, "focusedElement after switch to chrome with no focused element"); + + // switch focus to another tab when neither have an active focus + yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true, + "focusedWindow after tab switch from no focus to no focus"); + + focused = yield getFocusedElementForBrowser(browser1, false); + is(focused, "Focus is <none>", "after tab switch from no focus to no focus, first browser"); + focused = yield getFocusedElementForBrowser(browser2, true); + is(focused, "Focus is button2", "after tab switch from no focus to no focus, second browser"); + + // next, check whether navigating forward, focusing the urlbar and then + // navigating back maintains the focus in the urlbar. + yield expectFocusShift(() => focusElementInChild("button1", "focus"), + "window1", "button1", true, + "focus button"); + + yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage3)); + + // now go back again + gURLBar.focus(); + + yield new Promise((resolve, reject) => { + window.addEventListener("pageshow", function navigationOccured(event) { + window.removeEventListener("pageshow", navigationOccured, true); + resolve(); + }, true); + document.getElementById('Browser:Back').doCommand(); + }); + + is(window.document.activeElement, gURLBar.inputField, "urlbar still focused after navigating back"); + + // Document navigation with F6 does not yet work in mutli-process browsers. + if (!gMultiProcessBrowser) { + gURLBar.focus(); + actualEvents = new EventStore(); + _lastfocus = "urlbar"; + _lastfocuswindow = "main-window"; + + yield expectFocusShift(() => EventUtils.synthesizeKey("VK_F6", { }), + "window1", "html1", + true, "switch document forward with f6"); + + EventUtils.synthesizeKey("VK_F6", { }); + is(fm.focusedWindow, window, "switch document forward again with f6"); + + browser1.style.MozUserFocus = "ignore"; + browser1.clientWidth; + EventUtils.synthesizeKey("VK_F6", { }); + is(fm.focusedWindow, window, "switch document forward again with f6 when browser non-focusable"); + + browser1.style.MozUserFocus = "normal"; + browser1.clientWidth; + } + + window.removeEventListener("focus", _browser_tabfocus_test_eventOccured, true); + window.removeEventListener("blur", _browser_tabfocus_test_eventOccured, true); + + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); + + finish(); +}); + +function _browser_tabfocus_test_eventOccured(event) +{ + function getWindowDocId(target) + { + if (target == browser1.contentWindow || target == browser1.contentDocument) { + return "window1"; + } + if (target == browser2.contentWindow || target == browser2.contentDocument) { + return "window2"; + } + return "main-window"; + } + + var id; + + // Some focus events from the child bubble up? Ignore them. + if (Cu.isCrossProcessWrapper(event.originalTarget)) + return; + + if (event.target instanceof Window) + id = getWindowDocId(event.originalTarget) + "-window"; + else if (event.target instanceof Document) + id = getWindowDocId(event.originalTarget) + "-document"; + else if (event.target.id == "urlbar" && event.originalTarget.localName == "input") + id = "urlbar"; + else if (event.originalTarget.localName == "browser") + id = (event.originalTarget == browser1) ? "browser1" : "browser2"; + else if (event.originalTarget.localName == "tab") + id = (event.originalTarget == tab1) ? "tab1" : "tab2"; + else + id = event.originalTarget.id; + + actualEvents.push(event.type + ": " + id); + compareFocusResults(); +} + +function getId(element) +{ + if (!element) { + return null; + } + + if (element.localName == "browser") { + return element == browser1 ? "browser1" : "browser2"; + } + + if (element.localName == "tab") { + return element == tab1 ? "tab1" : "tab2"; + } + + return (element.localName == "input") ? "urlbar" : element.id; +} + +function compareFocusResults() +{ + if (!currentPromiseResolver) + return; + + let winIds = ["main-window", "window1", "window2"]; + + for (let winId of winIds) { + if (actualEvents[winId].length < expectedEvents[winId].length) + return; + } + + for (let winId of winIds) { + for (let e = 0; e < expectedEvents.length; e++) { + is(actualEvents[winId][e], expectedEvents[winId][e], currentTestName + " events [event " + e + "]"); + } + actualEvents[winId] = []; + } + + // Use executeSoon as this will be called during a focus/blur event handler + executeSoon(() => { + let matchWindow = window; + if (gMultiProcessBrowser) { + is(_expectedWindow, "main-window", "main-window is always expected"); + } + else if (_expectedWindow != "main-window") { + matchWindow = (_expectedWindow == "window1" ? browser1.contentWindow : browser2.contentWindow); + } + + var focusedElement = fm.focusedElement; + is(getId(focusedElement), _expectedElement, currentTestName + " focusedElement"); + is(fm.focusedWindow, matchWindow, currentTestName + " focusedWindow"); + var focusedWindow = {}; + is(getId(fm.getFocusedElementForWindow(matchWindow, false, focusedWindow)), + _expectedElement, currentTestName + " getFocusedElementForWindow"); + is(focusedWindow.value, matchWindow, currentTestName + " getFocusedElementForWindow frame"); + is(matchWindow.document.hasFocus(), true, currentTestName + " hasFocus"); + var expectedActive = _expectedElement; + if (!expectedActive) { + expectedActive = matchWindow.document instanceof XULDocument ? + "main-window" : getId(matchWindow.document.body); + } + is(getId(matchWindow.document.activeElement), expectedActive, currentTestName + " activeElement"); + + currentPromiseResolver(); + currentPromiseResolver = null; + }); +} + +function* expectFocusShiftAfterTabSwitch(tab, expectedWindow, expectedElement, focusChanged, testid) +{ + let tabSwitchPromise = null; + yield expectFocusShift(() => { tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, tab) }, + expectedWindow, expectedElement, focusChanged, testid) + yield tabSwitchPromise; +} + +function* expectFocusShift(callback, expectedWindow, expectedElement, focusChanged, testid) +{ + currentPromiseResolver = null; + currentTestName = testid; + + expectedEvents = new EventStore(); + + if (focusChanged) { + _expectedElement = expectedElement; + _expectedWindow = expectedWindow; + + // When the content is in a child process, the expected element in the chrome window + // will always be the urlbar or a browser element. + if (gMultiProcessBrowser) { + if (_expectedWindow == "window1") { + _expectedElement = "browser1"; + } + else if (_expectedWindow == "window2") { + _expectedElement = "browser2"; + } + _expectedWindow = "main-window"; + } + + if (gMultiProcessBrowser && _lastfocuswindow != "main-window" && + _lastfocuswindow != expectedWindow) { + let browserid = _lastfocuswindow == "window1" ? "browser1" : "browser2"; + expectedEvents.push("blur: " + browserid); + } + + var newElementIsFocused = (expectedElement && !expectedElement.startsWith("html")); + if (newElementIsFocused && gMultiProcessBrowser && + _lastfocuswindow != "main-window" && + expectedWindow == "main-window") { + // When switching from a child to a chrome element, the focus on the element will arrive first. + expectedEvents.push("focus: " + expectedElement); + newElementIsFocused = false; + } + + if (_lastfocus && _lastfocus != _expectedElement) + expectedEvents.push("blur: " + _lastfocus); + + if (_lastfocuswindow && + _lastfocuswindow != expectedWindow) { + + if (!gMultiProcessBrowser || _lastfocuswindow != "main-window") { + expectedEvents.push("blur: " + _lastfocuswindow + "-document"); + expectedEvents.push("blur: " + _lastfocuswindow + "-window"); + } + } + + if (expectedWindow && _lastfocuswindow != expectedWindow) { + if (gMultiProcessBrowser && expectedWindow != "main-window") { + let browserid = expectedWindow == "window1" ? "browser1" : "browser2"; + expectedEvents.push("focus: " + browserid); + } + + if (!gMultiProcessBrowser || expectedWindow != "main-window") { + expectedEvents.push("focus: " + expectedWindow + "-document"); + expectedEvents.push("focus: " + expectedWindow + "-window"); + } + } + + if (newElementIsFocused) { + expectedEvents.push("focus: " + expectedElement); + } + + _lastfocus = expectedElement; + _lastfocuswindow = expectedWindow; + } + + return new Promise((resolve, reject) => { + currentPromiseResolver = resolve; + callback(); + + // No events are expected, so resolve the promise immediately. + if (expectedEvents["main-window"].length + expectedEvents["window1"].length + expectedEvents["window2"].length == 0) { + currentPromiseResolver(); + currentPromiseResolver = null; + } + }); +} diff --git a/browser/base/content/test/general/browser_tabkeynavigation.js b/browser/base/content/test/general/browser_tabkeynavigation.js new file mode 100644 index 000000000..d8e51f4b9 --- /dev/null +++ b/browser/base/content/test/general/browser_tabkeynavigation.js @@ -0,0 +1,156 @@ +/* + * This test checks that keyboard navigation for tabs isn't blocked by content + */ +add_task(function* test() { + + let testPage1 = "data:text/html,<html id='tab1'><body><button id='button1'>Tab 1</button></body></html>"; + let testPage2 = "data:text/html,<html id='tab2'><body><button id='button2'>Tab 2</button><script>function preventDefault(event) { event.preventDefault(); event.stopImmediatePropagation(); } window.addEventListener('keydown', preventDefault, true); window.addEventListener('keypress', preventDefault, true);</script></body></html>"; + let testPage3 = "data:text/html,<html id='tab3'><body><button id='button3'>Tab 3</button></body></html>"; + + let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1); + let browser1 = gBrowser.getBrowserForTab(tab1); + let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2); + let tab3 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3); + + // Kill the animation for simpler test. + Services.prefs.setBoolPref("browser.tabs.animate", false); + + gBrowser.selectedTab = tab1; + browser1.focus(); + + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated"); + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+Tab on Tab1"); + + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true }); + is(gBrowser.selectedTab, tab3, + "Tab3 should be activated by pressing Ctrl+Tab on Tab2"); + + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+Shift+Tab on Tab3"); + + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: true }); + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated by pressing Ctrl+Shift+Tab on Tab2"); + + gBrowser.selectedTab = tab1; + browser1.focus(); + + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated"); + EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+PageDown on Tab1"); + + EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true }); + is(gBrowser.selectedTab, tab3, + "Tab3 should be activated by pressing Ctrl+PageDown on Tab2"); + + EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+PageUp on Tab3"); + + EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true }); + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated by pressing Ctrl+PageUp on Tab2"); + + if (gBrowser.mTabBox._handleMetaAltArrows) { + gBrowser.selectedTab = tab1; + browser1.focus(); + + let ltr = window.getComputedStyle(gBrowser.mTabBox, "").direction == "ltr"; + let advanceKey = ltr ? "VK_RIGHT" : "VK_LEFT"; + let reverseKey = ltr ? "VK_LEFT" : "VK_RIGHT"; + + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated"); + EventUtils.synthesizeKey(advanceKey, { altKey: true, metaKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+" + advanceKey + " on Tab1"); + + EventUtils.synthesizeKey(advanceKey, { altKey: true, metaKey: true }); + is(gBrowser.selectedTab, tab3, + "Tab3 should be activated by pressing Ctrl+" + advanceKey + " on Tab2"); + + EventUtils.synthesizeKey(reverseKey, { altKey: true, metaKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+" + reverseKey + " on Tab3"); + + EventUtils.synthesizeKey(reverseKey, { altKey: true, metaKey: true }); + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated by pressing Ctrl+" + reverseKey + " on Tab2"); + } + + gBrowser.selectedTab = tab2; + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated"); + is(gBrowser.tabContainer.selectedIndex, 2, + "Tab2 index should be 2"); + + EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true, shiftKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated after Ctrl+Shift+PageDown"); + is(gBrowser.tabContainer.selectedIndex, 3, + "Tab2 index should be 1 after Ctrl+Shift+PageDown"); + + EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true, shiftKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated after Ctrl+Shift+PageUp"); + is(gBrowser.tabContainer.selectedIndex, 2, + "Tab2 index should be 2 after Ctrl+Shift+PageUp"); + + if (navigator.platform.indexOf("Mac") == 0) { + gBrowser.selectedTab = tab1; + browser1.focus(); + + // XXX Currently, Command + "{" and "}" don't work if keydown event is + // consumed because following keypress event isn't fired. + + let ltr = window.getComputedStyle(gBrowser.mTabBox, "").direction == "ltr"; + let advanceKey = ltr ? "}" : "{"; + let reverseKey = ltr ? "{" : "}"; + + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated"); + + EventUtils.synthesizeKey(advanceKey, { metaKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+" + advanceKey + " on Tab1"); + + EventUtils.synthesizeKey(advanceKey, { metaKey: true }); + is(gBrowser.selectedTab, tab3, + "Tab3 should be activated by pressing Ctrl+" + advanceKey + " on Tab2"); + + EventUtils.synthesizeKey(reverseKey, { metaKey: true }); + is(gBrowser.selectedTab, tab2, + "Tab2 should be activated by pressing Ctrl+" + reverseKey + " on Tab3"); + + EventUtils.synthesizeKey(reverseKey, { metaKey: true }); + is(gBrowser.selectedTab, tab1, + "Tab1 should be activated by pressing Ctrl+" + reverseKey + " on Tab2"); + } else { + gBrowser.selectedTab = tab2; + EventUtils.synthesizeKey("VK_F4", { type: "keydown", ctrlKey: true }); + + isnot(gBrowser.selectedTab, tab2, + "Tab2 should be closed by pressing Ctrl+F4 on Tab2"); + is(gBrowser.tabs.length, 3, + "The count of tabs should be 3 since tab2 should be closed"); + + // NOTE: keypress event shouldn't be fired since the keydown event should + // be consumed by tab2. + EventUtils.synthesizeKey("VK_F4", { type: "keyup", ctrlKey: true }); + is(gBrowser.tabs.length, 3, + "The count of tabs should be 3 since renaming key events shouldn't close other tabs"); + } + + gBrowser.selectedTab = tab3; + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } + + Services.prefs.clearUserPref("browser.tabs.animate"); +}); diff --git a/browser/base/content/test/general/browser_tabopen_reflows.js b/browser/base/content/test/general/browser_tabopen_reflows.js new file mode 100644 index 000000000..8e04cf12e --- /dev/null +++ b/browser/base/content/test/general/browser_tabopen_reflows.js @@ -0,0 +1,157 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +XPCOMUtils.defineLazyGetter(this, "docShell", () => { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); +}); + +const EXPECTED_REFLOWS = [ + // tabbrowser.adjustTabstrip() call after tabopen animation has finished + "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" + + "_handleNewTab@chrome://browser/content/tabbrowser.xml|" + + "onxbltransitionend@chrome://browser/content/tabbrowser.xml|", + + // switching focus in updateCurrentBrowser() causes reflows + "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml|" + + "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" + + "onselect@chrome://browser/content/browser.xul|", + + // switching focus in openLinkIn() causes reflows + "openLinkIn@chrome://browser/content/utilityOverlay.js|" + + "openUILinkIn@chrome://browser/content/utilityOverlay.js|" + + "BrowserOpenTab@chrome://browser/content/browser.js|", + + // accessing element.scrollPosition in _fillTrailingGap() flushes layout + "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" + + "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" + + "_handleNewTab@chrome://browser/content/tabbrowser.xml|" + + "onxbltransitionend@chrome://browser/content/tabbrowser.xml|", + + // SessionStore.getWindowDimensions() + "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" + + "ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" + + "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" + + "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|", + + // selection change notification may cause querying the focused editor content + // by IME and that will cause reflow. + "select@chrome://global/content/bindings/textbox.xml|" + + "focusAndSelectUrlBar@chrome://browser/content/browser.js|" + + "openLinkIn@chrome://browser/content/utilityOverlay.js|" + + "openUILinkIn@chrome://browser/content/utilityOverlay.js|" + + "BrowserOpenTab@chrome://browser/content/browser.js|", + +]; + +const PREF_PRELOAD = "browser.newtab.preload"; +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source"; + +/* + * This test ensures that there are no unexpected + * uninterruptible reflows when opening new tabs. + */ +add_task(function*() { + let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider; + let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils; + let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; + + // resolves promise when directory links are downloaded and written to disk + function watchLinksChangeOnce() { + let deferred = Promise.defer(); + let observer = { + onManyLinksChanged: () => { + DirectoryLinksProvider.removeObserver(observer); + NewTabUtils.links.populateCache(() => { + NewTabUtils.allPages.update(); + deferred.resolve(); + }, true); + } + }; + observer.onDownloadFail = observer.onManyLinksChanged; + DirectoryLinksProvider.addObserver(observer); + return deferred.promise; + } + + let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE); + registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_PRELOAD); + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource); + return watchLinksChangeOnce(); + }); + + Services.prefs.setBoolPref(PREF_PRELOAD, false); + // set directory source to dummy/empty links + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}'); + + // run tests when directory source change completes + yield watchLinksChangeOnce(); + + // Perform a click in the top left of content to ensure the mouse isn't + // hovering over any of the tiles + let target = gBrowser.selectedBrowser; + let rect = target.getBoundingClientRect(); + let left = rect.left + 1; + let top = rect.top + 1; + + let utils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + + // Add a reflow observer and open a new tab. + docShell.addWeakReflowObserver(observer); + BrowserOpenTab(); + + // Wait until the tabopen animation has finished. + yield waitForTransitionEnd(); + + // Remove reflow observer and clean up. + docShell.removeWeakReflowObserver(observer); + gBrowser.removeCurrentTab(); +}); + +var observer = { + reflow: function (start, end) { + // Gather information about the current code path. + let path = (new Error().stack).split("\n").slice(1).map(line => { + return line.replace(/:\d+:\d+$/, ""); + }).join("|"); + let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|"); + + // Stack trace is empty. Reflow was triggered by native code. + if (path === "") { + return; + } + + // Check if this is an expected reflow. + for (let stack of EXPECTED_REFLOWS) { + if (path.startsWith(stack)) { + ok(true, "expected uninterruptible reflow '" + stack + "'"); + return; + } + } + + ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'"); + }, + + reflowInterruptible: function (start, end) { + // We're not interested in interruptible reflows. + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, + Ci.nsISupportsWeakReference]) +}; + +function waitForTransitionEnd() { + return new Promise(resolve => { + let tab = gBrowser.selectedTab; + tab.addEventListener("transitionend", function onEnd(event) { + if (event.propertyName === "max-width") { + tab.removeEventListener("transitionend", onEnd); + resolve(); + } + }); + }); +} diff --git a/browser/base/content/test/general/browser_tabs_close_beforeunload.js b/browser/base/content/test/general/browser_tabs_close_beforeunload.js new file mode 100644 index 000000000..b867efd72 --- /dev/null +++ b/browser/base/content/test/general/browser_tabs_close_beforeunload.js @@ -0,0 +1,49 @@ +"use strict"; + +SimpleTest.requestCompleteLog(); + +SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]}); + +const FIRST_TAB = getRootDirectory(gTestPath) + "close_beforeunload_opens_second_tab.html"; +const SECOND_TAB = getRootDirectory(gTestPath) + "close_beforeunload.html"; + +add_task(function*() { + info("Opening first tab"); + let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, FIRST_TAB); + let secondTabLoadedPromise; + let secondTab; + let tabOpened = new Promise(resolve => { + info("Adding tabopen listener"); + gBrowser.tabContainer.addEventListener("TabOpen", function tabOpenListener(e) { + info("Got tabopen, removing listener and waiting for load"); + gBrowser.tabContainer.removeEventListener("TabOpen", tabOpenListener, false, false); + secondTab = e.target; + secondTabLoadedPromise = BrowserTestUtils.browserLoaded(secondTab.linkedBrowser, false, SECOND_TAB); + resolve(); + }, false, false); + }); + info("Opening second tab using a click"); + yield ContentTask.spawn(firstTab.linkedBrowser, "", function*() { + content.document.getElementsByTagName("a")[0].click(); + }); + info("Waiting for the second tab to be opened"); + yield tabOpened; + info("Waiting for the load in that tab to finish"); + yield secondTabLoadedPromise; + + let closeBtn = document.getAnonymousElementByAttribute(secondTab, "anonid", "close-button"); + let closePromise = BrowserTestUtils.removeTab(secondTab, {dontRemove: true}); + info("closing second tab (which will self-close in beforeunload)"); + closeBtn.click(); + ok(secondTab.closing, "Second tab should be marked as closing synchronously."); + yield closePromise; + ok(secondTab.closing, "Second tab should still be marked as closing"); + ok(!secondTab.linkedBrowser, "Second tab's browser should be dead"); + ok(!firstTab.closing, "First tab should not be closing"); + ok(firstTab.linkedBrowser, "First tab's browser should be alive"); + info("closing first tab"); + yield BrowserTestUtils.removeTab(firstTab); + + ok(firstTab.closing, "First tab should be marked as closing"); + ok(!firstTab.linkedBrowser, "First tab's browser should be dead"); +}); diff --git a/browser/base/content/test/general/browser_tabs_isActive.js b/browser/base/content/test/general/browser_tabs_isActive.js new file mode 100644 index 000000000..0725757e7 --- /dev/null +++ b/browser/base/content/test/general/browser_tabs_isActive.js @@ -0,0 +1,152 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test for the docshell active state of local and remote browsers. + +const kTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + +function promiseNewTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener("TabSwitchDone", function onSwitch() { + gBrowser.removeEventListener("TabSwitchDone", onSwitch); + executeSoon(resolve); + }); + }); +} + +function getParentTabState(aTab) { + return aTab.linkedBrowser.docShellIsActive; +} + +function getChildTabState(aTab) { + return ContentTask.spawn(aTab.linkedBrowser, {}, function* () { + return docShell.isActive; + }); +} + +function checkState(parentSide, childSide, value, message) { + is(parentSide, value, message + " (parent side)"); + is(childSide, value, message + " (child side)"); +} + +function waitForMs(aMs) { + return new Promise((resolve) => { + setTimeout(done, aMs); + function done() { + resolve(true); + } + }); +} + +add_task(function *() { + let url = kTestPage; + let originalTab = gBrowser.selectedTab; // test tab + let newTab = gBrowser.addTab(url, {skipAnimation: true}); + let parentSide, childSide; + + // new tab added but not selected checks + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, false, "newly added " + url + " tab is not active"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, true, "original tab is active initially"); + + // select the newly added tab and wait for TabSwitchDone event + let tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = newTab; + yield tabSwitchedPromise; + + if (Services.appinfo.browserTabsRemoteAutostart) { + ok(newTab.linkedBrowser.isRemoteBrowser, "for testing we need a remote tab"); + } + + // check active state of both tabs + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, false, "original tab is not active while unselected"); + + // switch back to the original test tab and wait for TabSwitchDone event + tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = originalTab; + yield tabSwitchedPromise; + + // check active state of both tabs + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, true, "original tab is active again after switch back"); + + // switch to the new tab and wait for TabSwitchDone event + tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = newTab; + yield tabSwitchedPromise; + + // check active state of both tabs + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, false, "original tab is active again after switch back"); + + gBrowser.removeTab(newTab); +}); + +add_task(function *() { + let url = "about:about"; + let originalTab = gBrowser.selectedTab; // test tab + let newTab = gBrowser.addTab(url, {skipAnimation: true}); + let parentSide, childSide; + + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, false, "newly added " + url + " tab is not active"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, true, "original tab is active initially"); + + let tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = newTab; + yield tabSwitchedPromise; + + if (Services.appinfo.browserTabsRemoteAutostart) { + ok(!newTab.linkedBrowser.isRemoteBrowser, "for testing we need a local tab"); + } + + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, false, "original tab is not active while unselected"); + + tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = originalTab; + yield tabSwitchedPromise; + + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, true, "original tab is active again after switch back"); + + tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = newTab; + yield tabSwitchedPromise; + + parentSide = getParentTabState(newTab); + childSide = yield getChildTabState(newTab); + checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back"); + parentSide = getParentTabState(originalTab); + childSide = yield getChildTabState(originalTab); + checkState(parentSide, childSide, false, "original tab is active again after switch back"); + + gBrowser.removeTab(newTab); +}); diff --git a/browser/base/content/test/general/browser_tabs_owner.js b/browser/base/content/test/general/browser_tabs_owner.js new file mode 100644 index 000000000..300d783ba --- /dev/null +++ b/browser/base/content/test/general/browser_tabs_owner.js @@ -0,0 +1,44 @@ +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: gBrowser._finalizeTabSwitch is not a function"); + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: gBrowser._finalizeTabSwitch is not a function"); + +function test() { + gBrowser.addTab(); + gBrowser.addTab(); + gBrowser.addTab(); + + var tabs = gBrowser.tabs; + var owner; + + is(tabs.length, 4, "4 tabs are open"); + + owner = gBrowser.selectedTab = tabs[2]; + BrowserOpenTab(); + is(gBrowser.selectedTab, tabs[4], "newly opened tab is selected"); + gBrowser.removeCurrentTab(); + is(gBrowser.selectedTab, owner, "owner is selected"); + + owner = gBrowser.selectedTab; + BrowserOpenTab(); + gBrowser.selectedTab = tabs[1]; + gBrowser.selectedTab = tabs[4]; + gBrowser.removeCurrentTab(); + isnot(gBrowser.selectedTab, owner, "selecting a different tab clears the owner relation"); + + owner = gBrowser.selectedTab; + BrowserOpenTab(); + gBrowser.moveTabTo(gBrowser.selectedTab, 0); + gBrowser.removeCurrentTab(); + is(gBrowser.selectedTab, owner, "owner relatitionship persists when tab is moved"); + + while (tabs.length > 1) + gBrowser.removeCurrentTab(); +} diff --git a/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js new file mode 100644 index 000000000..f90f047d3 --- /dev/null +++ b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js @@ -0,0 +1,126 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const OPEN_LOCATION_PREF = "browser.link.open_newwindow"; +const NON_REMOTE_PAGE = "about:welcomeback"; + +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); + +requestLongerTimeout(2); + +function frame_script() { + content.document.body.innerHTML = ` + <a href="about:home" target="_blank" id="testAnchor">Open a window</a> + `; + + let element = content.document.getElementById("testAnchor"); + element.click(); +} + +/** + * Takes some browser in some window, and forces that browser + * to become non-remote, and then navigates it to a page that + * we're not supposed to be displaying remotely. Returns a + * Promise that resolves when the browser is no longer remote. + */ +function prepareNonRemoteBrowser(aWindow, browser) { + browser.loadURI(NON_REMOTE_PAGE); + return BrowserTestUtils.browserLoaded(browser); +} + +registerCleanupFunction(() => { + Services.prefs.clearUserPref(OPEN_LOCATION_PREF); +}); + +/** + * Test that if we open a new tab from a link in a non-remote + * browser in an e10s window, that the new tab will load properly. + */ +add_task(function* test_new_tab() { + let normalWindow = yield BrowserTestUtils.openNewBrowserWindow({ + remote: true, + }); + let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({ + remote: true, + private: true, + }); + + for (let testWindow of [normalWindow, privateWindow]) { + yield promiseWaitForFocus(testWindow); + let testBrowser = testWindow.gBrowser.selectedBrowser; + info("Preparing non-remote browser"); + yield prepareNonRemoteBrowser(testWindow, testBrowser); + info("Non-remote browser prepared - sending frame script"); + + // Get our framescript ready + let mm = testBrowser.messageManager; + mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true); + + let tabOpenEvent = yield waitForNewTabEvent(testWindow.gBrowser); + let newTab = tabOpenEvent.target; + + yield promiseTabLoadEvent(newTab); + + // Our framescript opens to about:home which means that the + // tab should eventually become remote. + ok(newTab.linkedBrowser.isRemoteBrowser, + "The opened browser never became remote."); + + testWindow.gBrowser.removeTab(newTab); + } + + normalWindow.close(); + privateWindow.close(); +}); + +/** + * Test that if we open a new window from a link in a non-remote + * browser in an e10s window, that the new window is not an e10s + * window. Also tests with a private browsing window. + */ +add_task(function* test_new_window() { + let normalWindow = yield BrowserTestUtils.openNewBrowserWindow({ + remote: true + }, true); + let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({ + remote: true, + private: true, + }, true); + + // Fiddle with the prefs so that we open target="_blank" links + // in new windows instead of new tabs. + Services.prefs.setIntPref(OPEN_LOCATION_PREF, + Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW); + + for (let testWindow of [normalWindow, privateWindow]) { + yield promiseWaitForFocus(testWindow); + let testBrowser = testWindow.gBrowser.selectedBrowser; + yield prepareNonRemoteBrowser(testWindow, testBrowser); + + // Get our framescript ready + let mm = testBrowser.messageManager; + mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true); + + // Click on the link in the browser, and wait for the new window. + let {subject: newWindow} = + yield promiseTopicObserved("browser-delayed-startup-finished"); + + is(PrivateBrowsingUtils.isWindowPrivate(testWindow), + PrivateBrowsingUtils.isWindowPrivate(newWindow), + "Private browsing state of new window does not match the original!"); + + let newTab = newWindow.gBrowser.selectedTab; + + yield promiseTabLoadEvent(newTab); + + // Our framescript opens to about:home which means that the + // tab should eventually become remote. + ok(newTab.linkedBrowser.isRemoteBrowser, + "The opened browser never became remote."); + newWindow.close(); + } + + normalWindow.close(); + privateWindow.close(); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_1.js b/browser/base/content/test/general/browser_trackingUI_1.js new file mode 100644 index 000000000..937d607af --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_1.js @@ -0,0 +1,170 @@ +/* + * Test that the Tracking Protection section is visible in the Control Center + * and has the correct state for the cases when: + * 1) A page with no tracking elements is loaded. + * 2) A page with tracking elements is loaded and they are blocked. + * 3) A page with tracking elements is loaded and they are not blocked. + * See also Bugs 1175327, 1043801, 1178985 + */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +const PREF = "privacy.trackingprotection.enabled"; +const PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html"; +const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html"; +var TrackingProtection = null; +var tabbrowser = null; + +var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {}); + +registerCleanupFunction(function() { + TrackingProtection = tabbrowser = null; + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(PREF); + Services.prefs.clearUserPref(PB_PREF); + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +function hidden(sel) { + let win = tabbrowser.ownerGlobal; + let el = win.document.querySelector(sel); + let display = win.getComputedStyle(el).getPropertyValue("display", null); + let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null); + return display === "none" || opacity === "0"; +} + +function clickButton(sel) { + let win = tabbrowser.ownerGlobal; + let el = win.document.querySelector(sel); + el.doCommand(); +} + +function testBenignPage() { + info("Non-tracking content must not be blocked"); + ok(!TrackingProtection.container.hidden, "The container is visible"); + ok(!TrackingProtection.content.hasAttribute("state"), "content: no state"); + ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state"); + ok(!TrackingProtection.icon.hasAttribute("tooltiptext"), "icon: no tooltip"); + + ok(hidden("#tracking-protection-icon"), "icon is hidden"); + ok(hidden("#tracking-action-block"), "blockButton is hidden"); + ok(hidden("#tracking-action-unblock"), "unblockButton is hidden"); + + // Make sure that the no tracking elements message appears + ok(!hidden("#tracking-not-detected"), "labelNoTracking is visible"); + ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden"); + ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden"); +} + +function testTrackingPage(window) { + info("Tracking content must be blocked"); + ok(!TrackingProtection.container.hidden, "The container is visible"); + is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content", + 'content: state="blocked-tracking-content"'); + is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content", + 'icon: state="blocked-tracking-content"'); + is(TrackingProtection.icon.getAttribute("tooltiptext"), + gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip"); + + ok(!hidden("#tracking-protection-icon"), "icon is visible"); + ok(hidden("#tracking-action-block"), "blockButton is hidden"); + + + if (PrivateBrowsingUtils.isWindowPrivate(window)) { + ok(hidden("#tracking-action-unblock"), "unblockButton is hidden"); + ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible"); + } else { + ok(!hidden("#tracking-action-unblock"), "unblockButton is visible"); + ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden"); + } + + // Make sure that the blocked tracking elements message appears + ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden"); + ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden"); + ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible"); +} + +function testTrackingPageUnblocked() { + info("Tracking content must be white-listed and not blocked"); + ok(!TrackingProtection.container.hidden, "The container is visible"); + is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content", + 'content: state="loaded-tracking-content"'); + is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content", + 'icon: state="loaded-tracking-content"'); + is(TrackingProtection.icon.getAttribute("tooltiptext"), + gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip"); + + ok(!hidden("#tracking-protection-icon"), "icon is visible"); + ok(!hidden("#tracking-action-block"), "blockButton is visible"); + ok(hidden("#tracking-action-unblock"), "unblockButton is hidden"); + + // Make sure that the blocked tracking elements message appears + ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden"); + ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible"); + ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden"); +} + +function* testTrackingProtectionForTab(tab) { + info("Load a test page not containing tracking elements"); + yield promiseTabLoadEvent(tab, BENIGN_PAGE); + testBenignPage(); + + info("Load a test page containing tracking elements"); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + testTrackingPage(tab.ownerGlobal); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + clickButton("#tracking-action-unblock"); + yield tabReloadPromise; + testTrackingPageUnblocked(); + + info("Re-enable TP for the page (which reloads the page)"); + tabReloadPromise = promiseTabLoadEvent(tab); + clickButton("#tracking-action-block"); + yield tabReloadPromise; + testTrackingPage(tab.ownerGlobal); +} + +add_task(function* testNormalBrowsing() { + yield UrlClassifierTestUtils.addTestTrackers(); + + tabbrowser = gBrowser; + let tab = tabbrowser.selectedTab = tabbrowser.addTab(); + + TrackingProtection = gBrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF), + "TP.enabled is based on the original pref value"); + + Services.prefs.setBoolPref(PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + yield testTrackingProtectionForTab(tab); + + Services.prefs.setBoolPref(PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled after setting the pref"); +}); + +add_task(function* testPrivateBrowsing() { + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + tabbrowser = privateWin.gBrowser; + let tab = tabbrowser.selectedTab = tabbrowser.addTab(); + + TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the private window"); + is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF), + "TP.enabled is based on the pb pref value"); + + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + yield testTrackingProtectionForTab(tab); + + Services.prefs.setBoolPref(PB_PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled after setting the pref"); + + privateWin.close(); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_2.js b/browser/base/content/test/general/browser_trackingUI_2.js new file mode 100644 index 000000000..96ccb6c2e --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_2.js @@ -0,0 +1,96 @@ +/* + * Test that the Tracking Protection section is never visible in the + * Control Center when the feature is off. + * See also Bugs 1175327, 1043801, 1178985. + */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +const PREF = "privacy.trackingprotection.enabled"; +const PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html"; +const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html"; +var TrackingProtection = null; +var tabbrowser = null; + +var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {}); + +registerCleanupFunction(function() { + TrackingProtection = tabbrowser = null; + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(PREF); + Services.prefs.clearUserPref(PB_PREF); + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +function hidden(el) { + let win = el.ownerGlobal; + let display = win.getComputedStyle(el).getPropertyValue("display", null); + let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null); + + return display === "none" || opacity === "0"; +} + +add_task(function* testNormalBrowsing() { + yield UrlClassifierTestUtils.addTestTrackers(); + + tabbrowser = gBrowser; + let {gIdentityHandler} = tabbrowser.ownerGlobal; + let tab = tabbrowser.selectedTab = tabbrowser.addTab(); + + TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF), + "TP.enabled is based on the original pref value"); + + Services.prefs.setBoolPref(PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + Services.prefs.setBoolPref(PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled after setting the pref"); + + info("Load a test page containing tracking elements"); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + gIdentityHandler._identityBox.click(); + ok(hidden(TrackingProtection.container), "The container is hidden"); + gIdentityHandler._identityPopup.hidden = true; + + info("Load a test page not containing tracking elements"); + yield promiseTabLoadEvent(tab, BENIGN_PAGE); + gIdentityHandler._identityBox.click(); + ok(hidden(TrackingProtection.container), "The container is hidden"); + gIdentityHandler._identityPopup.hidden = true; +}); + +add_task(function* testPrivateBrowsing() { + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + tabbrowser = privateWin.gBrowser; + let {gIdentityHandler} = tabbrowser.ownerGlobal; + let tab = tabbrowser.selectedTab = tabbrowser.addTab(); + + TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the private window"); + is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF), + "TP.enabled is based on the pb pref value"); + + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + Services.prefs.setBoolPref(PB_PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled after setting the pref"); + + info("Load a test page containing tracking elements"); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + gIdentityHandler._identityBox.click(); + ok(hidden(TrackingProtection.container), "The container is hidden"); + gIdentityHandler._identityPopup.hidden = true; + + info("Load a test page not containing tracking elements"); + gIdentityHandler._identityBox.click(); + yield promiseTabLoadEvent(tab, BENIGN_PAGE); + ok(hidden(TrackingProtection.container), "The container is hidden"); + gIdentityHandler._identityPopup.hidden = true; + + privateWin.close(); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_3.js b/browser/base/content/test/general/browser_trackingUI_3.js new file mode 100644 index 000000000..63f8a13bc --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_3.js @@ -0,0 +1,52 @@ +/* + * Test that the Tracking Protection is correctly enabled / disabled + * in both normal and private windows given all possible states of the prefs: + * privacy.trackingprotection.enabled + * privacy.trackingprotection.pbmode.enabled + * See also Bug 1178985. + */ + +const PREF = "privacy.trackingprotection.enabled"; +const PB_PREF = "privacy.trackingprotection.pbmode.enabled"; + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(PREF); + Services.prefs.clearUserPref(PB_PREF); +}); + +add_task(function* testNormalBrowsing() { + let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + + Services.prefs.setBoolPref(PREF, true); + Services.prefs.setBoolPref(PB_PREF, false); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)"); + + Services.prefs.setBoolPref(PREF, false); + Services.prefs.setBoolPref(PB_PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=true)"); +}); + +add_task(function* testPrivateBrowsing() { + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + let TrackingProtection = privateWin.gBrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + + Services.prefs.setBoolPref(PREF, true); + Services.prefs.setBoolPref(PB_PREF, false); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)"); + + Services.prefs.setBoolPref(PREF, false); + Services.prefs.setBoolPref(PB_PREF, false); + ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)"); + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled (ENABLED=false,PB=true)"); + + privateWin.close(); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_4.js b/browser/base/content/test/general/browser_trackingUI_4.js new file mode 100644 index 000000000..93a06913e --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_4.js @@ -0,0 +1,109 @@ +/* + * Test that the Tracking Protection icon is properly animated in the identity + * block when loading tabs and switching between tabs. + * See also Bug 1175858. + */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +const PREF = "privacy.trackingprotection.enabled"; +const PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html"; +const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html"; +var TrackingProtection = null; +var tabbrowser = null; + +var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {}); + +registerCleanupFunction(function() { + TrackingProtection = tabbrowser = null; + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.prefs.clearUserPref(PREF); + Services.prefs.clearUserPref(PB_PREF); + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +function waitForSecurityChange(numChanges = 1) { + return new Promise(resolve => { + let n = 0; + let listener = { + onSecurityChange: function() { + n = n + 1; + info ("Received onSecurityChange event " + n + " of " + numChanges); + if (n >= numChanges) { + tabbrowser.removeProgressListener(listener); + resolve(); + } + } + }; + tabbrowser.addProgressListener(listener); + }); +} + +function* testTrackingProtectionAnimation() { + info("Load a test page not containing tracking elements"); + let benignTab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE); + + ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state"); + ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate"); + + info("Load a test page containing tracking elements"); + let trackingTab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser, TRACKING_PAGE); + + ok(TrackingProtection.icon.hasAttribute("state"), "icon: state"); + ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate"); + + info("Switch from tracking -> benign tab"); + let securityChanged = waitForSecurityChange(); + tabbrowser.selectedTab = benignTab; + yield securityChanged; + + ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state"); + ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate"); + + info("Switch from benign -> tracking tab"); + securityChanged = waitForSecurityChange(); + tabbrowser.selectedTab = trackingTab; + yield securityChanged; + + ok(TrackingProtection.icon.hasAttribute("state"), "icon: state"); + ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate"); + + info("Reload tracking tab"); + securityChanged = waitForSecurityChange(2); + tabbrowser.reload(); + yield securityChanged; + + ok(TrackingProtection.icon.hasAttribute("state"), "icon: state"); + ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate"); +} + +add_task(function* testNormalBrowsing() { + yield UrlClassifierTestUtils.addTestTrackers(); + + tabbrowser = gBrowser; + + TrackingProtection = gBrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + + Services.prefs.setBoolPref(PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + yield testTrackingProtectionAnimation(); +}); + +add_task(function* testPrivateBrowsing() { + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + tabbrowser = privateWin.gBrowser; + + TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the private window"); + + Services.prefs.setBoolPref(PB_PREF, true); + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + yield testTrackingProtectionAnimation(); + + privateWin.close(); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_5.js b/browser/base/content/test/general/browser_trackingUI_5.js new file mode 100644 index 000000000..23164a5b2 --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_5.js @@ -0,0 +1,131 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that sites added to the Tracking Protection whitelist in private +// browsing mode don't persist once the private browsing window closes. + +const PB_PREF = "privacy.trackingprotection.pbmode.enabled"; +const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html"; +var TrackingProtection = null; +var browser = null; +var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {}); + +registerCleanupFunction(function() { + TrackingProtection = browser = null; + UrlClassifierTestUtils.cleanupTestTrackers(); +}); + +function hidden(sel) { + let win = browser.ownerGlobal; + let el = win.document.querySelector(sel); + let display = win.getComputedStyle(el).getPropertyValue("display", null); + return display === "none"; +} + +function identityPopupState() { + let win = browser.ownerGlobal; + return win.document.getElementById("identity-popup").state; +} + +function clickButton(sel) { + let win = browser.ownerGlobal; + let el = win.document.querySelector(sel); + el.doCommand(); +} + +function testTrackingPage(window) { + info("Tracking content must be blocked"); + ok(!TrackingProtection.container.hidden, "The container is visible"); + is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content", + 'content: state="blocked-tracking-content"'); + is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content", + 'icon: state="blocked-tracking-content"'); + + ok(!hidden("#tracking-protection-icon"), "icon is visible"); + ok(hidden("#tracking-action-block"), "blockButton is hidden"); + + ok(hidden("#tracking-action-unblock"), "unblockButton is hidden"); + ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible"); + + // Make sure that the blocked tracking elements message appears + ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden"); + ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden"); + ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible"); +} + +function testTrackingPageUnblocked() { + info("Tracking content must be white-listed and not blocked"); + ok(!TrackingProtection.container.hidden, "The container is visible"); + is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content", + 'content: state="loaded-tracking-content"'); + is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content", + 'icon: state="loaded-tracking-content"'); + + ok(!hidden("#tracking-protection-icon"), "icon is visible"); + ok(!hidden("#tracking-action-block"), "blockButton is visible"); + ok(hidden("#tracking-action-unblock"), "unblockButton is hidden"); + + // Make sure that the blocked tracking elements message appears + ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden"); + ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible"); + ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden"); +} + +add_task(function* testExceptionAddition() { + yield UrlClassifierTestUtils.addTestTrackers(); + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + browser = privateWin.gBrowser; + let tab = browser.selectedTab = browser.addTab(); + + TrackingProtection = browser.ownerGlobal.TrackingProtection; + yield pushPrefs([PB_PREF, true]); + + ok(TrackingProtection.enabled, "TP is enabled after setting the pref"); + + info("Load a test page containing tracking elements"); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + + testTrackingPage(tab.ownerGlobal); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + clickButton("#tracking-action-unblock"); + is(identityPopupState(), "closed", "foobar"); + + yield tabReloadPromise; + testTrackingPageUnblocked(); + + info("Test that the exception is remembered across tabs in the same private window"); + tab = browser.selectedTab = browser.addTab(); + + info("Load a test page containing tracking elements"); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + testTrackingPageUnblocked(); + + yield promiseWindowClosed(privateWin); +}); + +add_task(function* testExceptionPersistence() { + info("Open another private browsing window"); + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + browser = privateWin.gBrowser; + let tab = browser.selectedTab = browser.addTab(); + + TrackingProtection = browser.ownerGlobal.TrackingProtection; + ok(TrackingProtection.enabled, "TP is still enabled"); + + info("Load a test page containing tracking elements"); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + + testTrackingPage(tab.ownerGlobal); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + clickButton("#tracking-action-unblock"); + is(identityPopupState(), "closed", "foobar"); + + yield tabReloadPromise; + testTrackingPageUnblocked(); + + privateWin.close(); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_6.js b/browser/base/content/test/general/browser_trackingUI_6.js new file mode 100644 index 000000000..be91bc4a0 --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_6.js @@ -0,0 +1,46 @@ +const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_trackingUI_6.html"; + +function waitForSecurityChange(numChanges = 1) { + return new Promise(resolve => { + let n = 0; + let listener = { + onSecurityChange: function() { + n = n + 1; + info ("Received onSecurityChange event " + n + " of " + numChanges); + if (n >= numChanges) { + gBrowser.removeProgressListener(listener); + resolve(); + } + } + }; + gBrowser.addProgressListener(listener); + }); +} + +add_task(function* test_fetch() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({ set: [['privacy.trackingprotection.enabled', true]] }, + resolve); + }); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, function* (newTabBrowser) { + let securityChange = waitForSecurityChange(); + yield ContentTask.spawn(newTabBrowser, null, function* () { + yield content.wrappedJSObject.test_fetch() + .then(response => Assert.ok(false, "should have denied the request")) + .catch(e => Assert.ok(true, `Caught exception: ${e}`)); + }); + yield securityChange; + + var TrackingProtection = newTabBrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "got TP object"); + ok(TrackingProtection.enabled, "TP is enabled"); + + is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content", + 'content: state="blocked-tracking-content"'); + is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content", + 'icon: state="blocked-tracking-content"'); + is(TrackingProtection.icon.getAttribute("tooltiptext"), + gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip"); + }); +}); diff --git a/browser/base/content/test/general/browser_trackingUI_telemetry.js b/browser/base/content/test/general/browser_trackingUI_telemetry.js new file mode 100644 index 000000000..d9fce18d4 --- /dev/null +++ b/browser/base/content/test/general/browser_trackingUI_telemetry.js @@ -0,0 +1,145 @@ +/* + * Test telemetry for Tracking Protection + */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +const PREF = "privacy.trackingprotection.enabled"; +const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html"; +const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html"; +const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {}); + +/** + * Enable local telemetry recording for the duration of the tests. + */ +var oldCanRecord = Services.telemetry.canRecordExtended; +Services.telemetry.canRecordExtended = true; +Services.prefs.setBoolPref(PREF, false); +Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").clear(); +registerCleanupFunction(function () { + UrlClassifierTestUtils.cleanupTestTrackers(); + Services.telemetry.canRecordExtended = oldCanRecord; + Services.prefs.clearUserPref(PREF); +}); + +function getShieldHistogram() { + return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD"); +} + +function getEnabledHistogram() { + return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED"); +} + +function getEventsHistogram() { + return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS"); +} + +function getShieldCounts() { + return getShieldHistogram().snapshot().counts; +} + +function getEnabledCounts() { + return getEnabledHistogram().snapshot().counts; +} + +function getEventCounts() { + return getEventsHistogram().snapshot().counts; +} + +add_task(function* setup() { + yield UrlClassifierTestUtils.addTestTrackers(); + + let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + ok(!TrackingProtection.enabled, "TP is not enabled"); + + // Open a window with TP disabled to make sure 'enabled' is logged correctly. + let newWin = yield promiseOpenAndLoadWindow({}, true); + yield promiseWindowClosed(newWin); + + is(getEnabledCounts()[0], 1, "TP was disabled once on start up"); + is(getEnabledCounts()[1], 0, "TP was not enabled on start up"); + + // Enable TP so the next browser to open will log 'enabled' + Services.prefs.setBoolPref(PREF, true); +}); + + +add_task(function* testNewWindow() { + let newWin = yield promiseOpenAndLoadWindow({}, true); + let tab = newWin.gBrowser.selectedTab = newWin.gBrowser.addTab(); + let TrackingProtection = newWin.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + + is(getEnabledCounts()[0], 1, "TP was disabled once on start up"); + is(getEnabledCounts()[1], 1, "TP was enabled once on start up"); + + // Reset these to make counting easier + getEventsHistogram().clear(); + getShieldHistogram().clear(); + + yield promiseTabLoadEvent(tab, BENIGN_PAGE); + is(getEventCounts()[0], 1, "Total page loads"); + is(getEventCounts()[1], 0, "Disable actions"); + is(getEventCounts()[2], 0, "Enable actions"); + is(getShieldCounts()[0], 1, "Page loads without tracking"); + + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + // Note that right now the events and shield histogram is not measuring what + // you might think. Since onSecurityChange fires twice for a tracking page, + // the total page loads count is double counting, and the shield count + // (which is meant to measure times when the shield wasn't shown) fires even + // when tracking elements exist on the page. + todo_is(getEventCounts()[0], 2, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING"); + is(getEventCounts()[1], 0, "Disable actions"); + is(getEventCounts()[2], 0, "Enable actions"); + todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING"); + + info("Disable TP for the page (which reloads the page)"); + let tabReloadPromise = promiseTabLoadEvent(tab); + newWin.document.querySelector("#tracking-action-unblock").doCommand(); + yield tabReloadPromise; + todo_is(getEventCounts()[0], 3, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING"); + is(getEventCounts()[1], 1, "Disable actions"); + is(getEventCounts()[2], 0, "Enable actions"); + todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING"); + + info("Re-enable TP for the page (which reloads the page)"); + tabReloadPromise = promiseTabLoadEvent(tab); + newWin.document.querySelector("#tracking-action-block").doCommand(); + yield tabReloadPromise; + todo_is(getEventCounts()[0], 4, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING"); + is(getEventCounts()[1], 1, "Disable actions"); + is(getEventCounts()[2], 1, "Enable actions"); + todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING"); + + yield promiseWindowClosed(newWin); + + // Reset these to make counting easier for the next test + getEventsHistogram().clear(); + getShieldHistogram().clear(); + getEnabledHistogram().clear(); +}); + +add_task(function* testPrivateBrowsing() { + let privateWin = yield promiseOpenAndLoadWindow({private: true}, true); + let tab = privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(); + let TrackingProtection = privateWin.TrackingProtection; + ok(TrackingProtection, "TP is attached to the browser window"); + + // Do a bunch of actions and make sure that no telemetry data is gathered + yield promiseTabLoadEvent(tab, BENIGN_PAGE); + yield promiseTabLoadEvent(tab, TRACKING_PAGE); + let tabReloadPromise = promiseTabLoadEvent(tab); + privateWin.document.querySelector("#tracking-action-unblock").doCommand(); + yield tabReloadPromise; + tabReloadPromise = promiseTabLoadEvent(tab); + privateWin.document.querySelector("#tracking-action-block").doCommand(); + yield tabReloadPromise; + + // Sum up all the counts to make sure that nothing got logged + is(getEnabledCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode"); + is(getEventCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode"); + is(getShieldCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode"); + + yield promiseWindowClosed(privateWin); +}); diff --git a/browser/base/content/test/general/browser_typeAheadFind.js b/browser/base/content/test/general/browser_typeAheadFind.js new file mode 100644 index 000000000..1d550944a --- /dev/null +++ b/browser/base/content/test/general/browser_typeAheadFind.js @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function *() { + let testWindow = yield BrowserTestUtils.openNewBrowserWindow(); + + testWindow.gBrowser.loadURI("data:text/html,<h1>A Page</h1>"); + yield BrowserTestUtils.browserLoaded(testWindow.gBrowser.selectedBrowser); + + yield SimpleTest.promiseFocus(testWindow.gBrowser.selectedBrowser); + + ok(!testWindow.gFindBarInitialized, "find bar is not initialized"); + + let findBarOpenPromise = promiseWaitForEvent(testWindow.gBrowser, "findbaropen"); + EventUtils.synthesizeKey("/", {}, testWindow); + yield findBarOpenPromise; + + ok(testWindow.gFindBarInitialized, "find bar is now initialized"); + + yield BrowserTestUtils.closeWindow(testWindow); +}); diff --git a/browser/base/content/test/general/browser_unknownContentType_title.js b/browser/base/content/test/general/browser_unknownContentType_title.js new file mode 100644 index 000000000..269406bdb --- /dev/null +++ b/browser/base/content/test/general/browser_unknownContentType_title.js @@ -0,0 +1,33 @@ +const url = "data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3Ctitle%3ETest%20Page%3C%2Ftitle%3E%3C%2Fhead%3E%3C%2Fhtml%3E"; +const unknown_url = "http://example.com/browser/browser/base/content/test/general/unknownContentType_file.pif"; + +function waitForNewWindow() { + return new Promise(resolve => { + let listener = (win) => { + Services.obs.removeObserver(listener, "toplevel-window-ready"); + win.addEventListener("load", () => { + resolve(win); + }); + }; + + Services.obs.addObserver(listener, "toplevel-window-ready", false) + }); +} + +add_task(function*() { + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + yield promiseTabLoaded(gBrowser.selectedTab); + + is(gBrowser.contentTitle, "Test Page", "Should have the right title.") + + browser.loadURI(unknown_url); + let win = yield waitForNewWindow(); + is(win.location, "chrome://mozapps/content/downloads/unknownContentType.xul", + "Should have seen the unknown content dialog."); + is(gBrowser.contentTitle, "Test Page", "Should still have the right title.") + + win.close(); + yield promiseWaitForFocus(window); + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/general/browser_unloaddialogs.js b/browser/base/content/test/general/browser_unloaddialogs.js new file mode 100644 index 000000000..bf3790b95 --- /dev/null +++ b/browser/base/content/test/general/browser_unloaddialogs.js @@ -0,0 +1,41 @@ +var testUrls = + [ + "data:text/html,<script>" + + "function handle(evt) {" + + "evt.target.removeEventListener(evt.type, handle, true);" + + "try { alert('This should NOT appear'); } catch(e) { }" + + "}" + + "window.addEventListener('pagehide', handle, true);" + + "window.addEventListener('beforeunload', handle, true);" + + "window.addEventListener('unload', handle, true);" + + "</script><body>Testing alert during pagehide/beforeunload/unload</body>", + "data:text/html,<script>" + + "function handle(evt) {" + + "evt.target.removeEventListener(evt.type, handle, true);" + + "try { prompt('This should NOT appear'); } catch(e) { }" + + "}" + + "window.addEventListener('pagehide', handle, true);" + + "window.addEventListener('beforeunload', handle, true);" + + "window.addEventListener('unload', handle, true);" + + "</script><body>Testing prompt during pagehide/beforeunload/unload</body>", + "data:text/html,<script>" + + "function handle(evt) {" + + "evt.target.removeEventListener(evt.type, handle, true);" + + "try { confirm('This should NOT appear'); } catch(e) { }" + + "}" + + "window.addEventListener('pagehide', handle, true);" + + "window.addEventListener('beforeunload', handle, true);" + + "window.addEventListener('unload', handle, true);" + + "</script><body>Testing confirm during pagehide/beforeunload/unload</body>", + ]; + +add_task(function*() { + for (let url of testUrls) { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + ok(true, "Loaded page " + url); + // Wait one turn of the event loop before closing, so everything settles. + yield new Promise(resolve => setTimeout(resolve, 0)); + yield BrowserTestUtils.removeTab(tab); + ok(true, "Closed page " + url + " without timeout"); + } +}); diff --git a/browser/base/content/test/general/browser_utilityOverlay.js b/browser/base/content/test/general/browser_utilityOverlay.js new file mode 100644 index 000000000..34adc00d9 --- /dev/null +++ b/browser/base/content/test/general/browser_utilityOverlay.js @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const gTests = [ + test_eventMatchesKey, + test_getTopWin, + test_getBoolPref, + test_openNewTabWith, + test_openUILink +]; + +function test () { + waitForExplicitFinish(); + executeSoon(runNextTest); +} + +function runNextTest() { + if (gTests.length) { + let testFun = gTests.shift(); + info("Running " + testFun.name); + testFun() + } + else { + finish(); + } +} + +function test_eventMatchesKey() { + let eventMatchResult; + let key; + let checkEvent = function(e) { + e.stopPropagation(); + e.preventDefault(); + eventMatchResult = eventMatchesKey(e, key); + } + document.addEventListener("keypress", checkEvent); + + try { + key = document.createElement("key"); + let keyset = document.getElementById("mainKeyset"); + key.setAttribute("key", "t"); + key.setAttribute("modifiers", "accel"); + keyset.appendChild(key); + EventUtils.synthesizeKey("t", {accelKey: true}); + is(eventMatchResult, true, "eventMatchesKey: one modifier"); + keyset.removeChild(key); + + key = document.createElement("key"); + key.setAttribute("key", "g"); + key.setAttribute("modifiers", "accel,shift"); + keyset.appendChild(key); + EventUtils.synthesizeKey("g", {accelKey: true, shiftKey: true}); + is(eventMatchResult, true, "eventMatchesKey: combination modifiers"); + keyset.removeChild(key); + + key = document.createElement("key"); + key.setAttribute("key", "w"); + key.setAttribute("modifiers", "accel"); + keyset.appendChild(key); + EventUtils.synthesizeKey("f", {accelKey: true}); + is(eventMatchResult, false, "eventMatchesKey: mismatch keys"); + keyset.removeChild(key); + + key = document.createElement("key"); + key.setAttribute("keycode", "VK_DELETE"); + keyset.appendChild(key); + EventUtils.synthesizeKey("VK_DELETE", {accelKey: true}); + is(eventMatchResult, false, "eventMatchesKey: mismatch modifiers"); + keyset.removeChild(key); + } finally { + // Make sure to remove the event listener so future tests don't + // fail when they simulate key presses. + document.removeEventListener("keypress", checkEvent); + } + + runNextTest(); +} + +function test_getTopWin() { + is(getTopWin(), window, "got top window"); + runNextTest(); +} + + +function test_getBoolPref() { + is(getBoolPref("browser.search.openintab", false), false, "getBoolPref"); + is(getBoolPref("this.pref.doesnt.exist", true), true, "getBoolPref fallback"); + is(getBoolPref("this.pref.doesnt.exist", false), false, "getBoolPref fallback #2"); + runNextTest(); +} + +function test_openNewTabWith() { + openNewTabWith("http://example.com/"); + let tab = gBrowser.selectedTab = gBrowser.tabs[1]; + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + is(tab.linkedBrowser.currentURI.spec, "http://example.com/", "example.com loaded"); + gBrowser.removeCurrentTab(); + runNextTest(); + }); +} + +function test_openUILink() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + is(tab.linkedBrowser.currentURI.spec, "http://example.org/", "example.org loaded"); + gBrowser.removeCurrentTab(); + runNextTest(); + }); + + openUILink("http://example.org/"); // defaults to "current" +} diff --git a/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js new file mode 100644 index 000000000..c8f3cdc96 --- /dev/null +++ b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js @@ -0,0 +1,55 @@ +function wait_while_tab_is_busy() { + return new Promise(resolve => { + let progressListener = { + onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + gBrowser.removeProgressListener(this); + setTimeout(resolve, 0); + } + } + }; + gBrowser.addProgressListener(progressListener); + }); +} + +// This function waits for the tab to stop being busy instead of waiting for it +// to load, since the canViewSource change happens at that time. +var with_new_tab_opened = Task.async(function* (options, taskFn) { + let busyPromise = wait_while_tab_is_busy(); + let tab = yield BrowserTestUtils.openNewForegroundTab(options.gBrowser, options.url, false); + yield busyPromise; + yield taskFn(tab.linkedBrowser); + gBrowser.removeTab(tab); +}); + +add_task(function*() { + yield new Promise((resolve) => { + SpecialPowers.pushPrefEnv({"set": [ + ["view_source.tab", true], + ]}, resolve); + }); +}); + +add_task(function* test_regular_page() { + function* test_expect_view_source_enabled(browser) { + ok(!XULBrowserWindow.canViewSource.hasAttribute("disabled"), + "View Source should be enabled"); + } + + yield with_new_tab_opened({ + gBrowser, + url: "http://example.com", + }, test_expect_view_source_enabled); +}); + +add_task(function* test_view_source_page() { + function* test_expect_view_source_disabled(browser) { + ok(XULBrowserWindow.canViewSource.hasAttribute("disabled"), + "View Source should be disabled"); + } + + yield with_new_tab_opened({ + gBrowser, + url: "view-source:http://example.com", + }, test_expect_view_source_disabled); +}); diff --git a/browser/base/content/test/general/browser_visibleFindSelection.js b/browser/base/content/test/general/browser_visibleFindSelection.js new file mode 100644 index 000000000..630490644 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleFindSelection.js @@ -0,0 +1,52 @@ +add_task(function*() { + const childContent = "<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>" + + "div</div><div style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'>" + + "<span id='s'>div</span></div>"; + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + + yield promiseTabLoadEvent(tab, "data:text/html," + escape(childContent)); + yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW); + + let findBarOpenPromise = promiseWaitForEvent(gBrowser, "findbaropen"); + EventUtils.synthesizeKey("f", { accelKey: true }); + yield findBarOpenPromise; + + ok(gFindBarInitialized, "find bar is now initialized"); + + // Finds the div in the green box. + let scrollPromise = promiseWaitForEvent(gBrowser, "scroll"); + EventUtils.synthesizeKey("d", {}); + EventUtils.synthesizeKey("i", {}); + EventUtils.synthesizeKey("v", {}); + yield scrollPromise; + + // Wait for one paint to ensure we've processed the previous key events and scrolling. + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + return new Promise( + resolve => { + content.requestAnimationFrame(() => { + setTimeout(resolve, 0); + }); + } + ); + }); + + // Finds the div in the red box. + scrollPromise = promiseWaitForEvent(gBrowser, "scroll"); + EventUtils.synthesizeKey("g", { accelKey: true }); + yield scrollPromise; + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + Assert.ok(content.document.getElementById("s").getBoundingClientRect().left >= 0, + "scroll should include find result"); + }); + + // clear the find bar + EventUtils.synthesizeKey("a", { accelKey: true }); + EventUtils.synthesizeKey("VK_DELETE", { }); + + gFindBar.close(); + gBrowser.removeCurrentTab(); +}); + diff --git a/browser/base/content/test/general/browser_visibleTabs.js b/browser/base/content/test/general/browser_visibleTabs.js new file mode 100644 index 000000000..e9130bc18 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs.js @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function* () { + // There should be one tab when we start the test + let [origTab] = gBrowser.visibleTabs; + + // Add a tab that will get pinned + let pinned = gBrowser.addTab(); + gBrowser.pinTab(pinned); + + let testTab = gBrowser.addTab(); + + let visible = gBrowser.visibleTabs; + is(visible.length, 3, "3 tabs should be open"); + is(visible[0], pinned, "the pinned tab is first"); + is(visible[1], origTab, "original tab is next"); + is(visible[2], testTab, "last created tab is last"); + + // Only show the test tab (but also get pinned and selected) + is(gBrowser.selectedTab, origTab, "sanity check that we're on the original tab"); + gBrowser.showOnlyTheseTabs([testTab]); + is(gBrowser.visibleTabs.length, 3, "all 3 tabs are still visible"); + + // Select the test tab and only show that (and pinned) + gBrowser.selectedTab = testTab; + gBrowser.showOnlyTheseTabs([testTab]); + + visible = gBrowser.visibleTabs; + is(visible.length, 2, "2 tabs should be visible including the pinned"); + is(visible[0], pinned, "first is pinned"); + is(visible[1], testTab, "next is the test tab"); + is(gBrowser.tabs.length, 3, "3 tabs should still be open"); + + gBrowser.selectTabAtIndex(1); + is(gBrowser.selectedTab, testTab, "second tab is the test tab"); + gBrowser.selectTabAtIndex(0); + is(gBrowser.selectedTab, pinned, "first tab is pinned"); + gBrowser.selectTabAtIndex(2); + is(gBrowser.selectedTab, testTab, "no third tab, so no change"); + gBrowser.selectTabAtIndex(0); + is(gBrowser.selectedTab, pinned, "switch back to the pinned"); + gBrowser.selectTabAtIndex(2); + is(gBrowser.selectedTab, testTab, "no third tab, so select last tab"); + gBrowser.selectTabAtIndex(-2); + is(gBrowser.selectedTab, pinned, "pinned tab is second from left (when orig tab is hidden)"); + gBrowser.selectTabAtIndex(-1); + is(gBrowser.selectedTab, testTab, "last tab is the test tab"); + + gBrowser.tabContainer.advanceSelectedTab(1, true); + is(gBrowser.selectedTab, pinned, "wrapped around the end to pinned"); + gBrowser.tabContainer.advanceSelectedTab(1, true); + is(gBrowser.selectedTab, testTab, "next to test tab"); + gBrowser.tabContainer.advanceSelectedTab(1, true); + is(gBrowser.selectedTab, pinned, "next to pinned again"); + + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(gBrowser.selectedTab, testTab, "going backwards to last tab"); + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(gBrowser.selectedTab, pinned, "next to pinned"); + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(gBrowser.selectedTab, testTab, "next to test tab again"); + + // Try showing all tabs + gBrowser.showOnlyTheseTabs(Array.slice(gBrowser.tabs)); + is(gBrowser.visibleTabs.length, 3, "all 3 tabs are visible again"); + + // Select the pinned tab and show the testTab to make sure selection updates + gBrowser.selectedTab = pinned; + gBrowser.showOnlyTheseTabs([testTab]); + is(gBrowser.tabs[1], origTab, "make sure origTab is in the middle"); + is(origTab.hidden, true, "make sure it's hidden"); + gBrowser.removeTab(pinned); + is(gBrowser.selectedTab, testTab, "making sure origTab was skipped"); + is(gBrowser.visibleTabs.length, 1, "only testTab is there"); + + // Only show one of the non-pinned tabs (but testTab is selected) + gBrowser.showOnlyTheseTabs([origTab]); + is(gBrowser.visibleTabs.length, 2, "got 2 tabs"); + + // Now really only show one of the tabs + gBrowser.showOnlyTheseTabs([testTab]); + visible = gBrowser.visibleTabs; + is(visible.length, 1, "only the original tab is visible"); + is(visible[0], testTab, "it's the original tab"); + is(gBrowser.tabs.length, 2, "still have 2 open tabs"); + + // Close the last visible tab and make sure we still get a visible tab + gBrowser.removeTab(testTab); + is(gBrowser.visibleTabs.length, 1, "only orig is left and visible"); + is(gBrowser.tabs.length, 1, "sanity check that it matches"); + is(gBrowser.selectedTab, origTab, "got the orig tab"); + is(origTab.hidden, false, "and it's not hidden -- visible!"); +}); diff --git a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js new file mode 100644 index 000000000..827f86c05 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + let tabOne = gBrowser.addTab("about:blank"); + let tabTwo = gBrowser.addTab("http://mochi.test:8888/"); + gBrowser.selectedTab = tabTwo; + + let browser = gBrowser.getBrowserForTab(tabTwo); + let onLoad = function() { + browser.removeEventListener("load", onLoad, true); + + gBrowser.showOnlyTheseTabs([tabTwo]); + + is(gBrowser.visibleTabs.length, 1, "Only one tab is visible"); + + let uris = PlacesCommandHook.uniqueCurrentPages; + is(uris.length, 1, "Only one uri is returned"); + + is(uris[0].uri.spec, tabTwo.linkedBrowser.currentURI.spec, "It's the correct URI"); + + gBrowser.removeTab(tabOne); + gBrowser.removeTab(tabTwo); + Array.forEach(gBrowser.tabs, function(tab) { + gBrowser.showTab(tab); + }); + + finish(); + } + browser.addEventListener("load", onLoad, true); +} diff --git a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js new file mode 100644 index 000000000..0a0ea87bd --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + // There should be one tab when we start the test + let [origTab] = gBrowser.visibleTabs; + is(gBrowser.visibleTabs.length, 1, "1 tab should be open"); + is(Disabled(), true, "Bookmark All Tabs should be disabled"); + + // Add a tab + let testTab1 = gBrowser.addTab(); + is(gBrowser.visibleTabs.length, 2, "2 tabs should be open"); + is(Disabled(), true, "Bookmark All Tabs should be disabled since there are two tabs with the same address"); + + let testTab2 = gBrowser.addTab("about:mozilla"); + is(gBrowser.visibleTabs.length, 3, "3 tabs should be open"); + // Wait for tab load, the code checks for currentURI. + testTab2.linkedBrowser.addEventListener("load", function () { + testTab2.linkedBrowser.removeEventListener("load", arguments.callee, true); + is(Disabled(), false, "Bookmark All Tabs should be enabled since there are two tabs with different addresses"); + + // Hide the original tab + gBrowser.selectedTab = testTab2; + gBrowser.showOnlyTheseTabs([testTab2]); + is(gBrowser.visibleTabs.length, 1, "1 tab should be visible"); + is(Disabled(), true, "Bookmark All Tabs should be disabled as there is only one visible tab"); + + // Add a tab that will get pinned + let pinned = gBrowser.addTab(); + is(gBrowser.visibleTabs.length, 2, "2 tabs should be visible now"); + is(Disabled(), false, "Bookmark All Tabs should be available as there are two visible tabs"); + gBrowser.pinTab(pinned); + is(Hidden(), false, "Bookmark All Tabs should be visible on a normal tab"); + is(Disabled(), true, "Bookmark All Tabs should not be available since one tab is pinned"); + gBrowser.selectedTab = pinned; + is(Hidden(), true, "Bookmark All Tabs should be hidden on a pinned tab"); + + // Show all tabs + let allTabs = Array.from(gBrowser.tabs); + gBrowser.showOnlyTheseTabs(allTabs); + + // reset the environment + gBrowser.removeTab(testTab2); + gBrowser.removeTab(testTab1); + gBrowser.removeTab(pinned); + is(gBrowser.visibleTabs.length, 1, "only orig is left and visible"); + is(gBrowser.tabs.length, 1, "sanity check that it matches"); + is(Disabled(), true, "Bookmark All Tabs should be hidden"); + is(gBrowser.selectedTab, origTab, "got the orig tab"); + is(origTab.hidden, false, "and it's not hidden -- visible!"); + finish(); + }, true); +} + +function Disabled() { + updateTabContextMenu(); + return document.getElementById("Browser:BookmarkAllTabs").getAttribute("disabled") == "true"; +} + +function Hidden() { + updateTabContextMenu(); + return document.getElementById("context_bookmarkAllTabs").hidden; +} diff --git a/browser/base/content/test/general/browser_visibleTabs_contextMenu.js b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js new file mode 100644 index 000000000..4fdab3d8a --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ]; + +add_task(function* test() { + // There should be one tab when we start the test + let [origTab] = gBrowser.visibleTabs; + is(gBrowser.visibleTabs.length, 1, "there is one visible tab"); + let testTab = gBrowser.addTab(); + is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs"); + + // Check the context menu with two tabs + updateTabContextMenu(origTab); + is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled"); + is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled"); + + + if (gFxAccounts.sendTabToDeviceEnabled) { + // Check the send tab to device menu item + const oldGetter = setupRemoteClientsFixture(remoteClientsFixture); + yield updateTabContextMenu(origTab, function* () { + yield openMenuItemSubmenu("context_sendTabToDevice"); + }); + is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown"); + let targets = document.getElementById("context_sendTabToDevicePopupMenu").childNodes; + is(targets[0].getAttribute("label"), "Foo", "Foo target is present"); + is(targets[1].getAttribute("label"), "Bar", "Bar target is present"); + is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present"); + restoreRemoteClients(oldGetter); + } + + // Hide the original tab. + gBrowser.selectedTab = testTab; + gBrowser.showOnlyTheseTabs([testTab]); + is(gBrowser.visibleTabs.length, 1, "now there is only one visible tab"); + + // Check the context menu with one tab. + updateTabContextMenu(testTab); + is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled when more than one tab exists"); + is(document.getElementById("context_reloadAllTabs").disabled, true, "Reload All Tabs is disabled"); + + // Add a tab that will get pinned + // So now there's one pinned tab, one visible unpinned tab, and one hidden tab + let pinned = gBrowser.addTab(); + gBrowser.pinTab(pinned); + is(gBrowser.visibleTabs.length, 2, "now there are two visible tabs"); + + // Check the context menu on the unpinned visible tab + updateTabContextMenu(testTab); + is(document.getElementById("context_closeOtherTabs").disabled, true, "Close Other Tabs is disabled"); + is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled"); + + // Show all tabs + let allTabs = Array.from(gBrowser.tabs); + gBrowser.showOnlyTheseTabs(allTabs); + + // Check the context menu now + updateTabContextMenu(testTab); + is(document.getElementById("context_closeOtherTabs").disabled, false, "Close Other Tabs is enabled"); + is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled"); + + // Check the context menu of the original tab + // Close Tabs To The End should now be enabled + updateTabContextMenu(origTab); + is(document.getElementById("context_closeTabsToTheEnd").disabled, false, "Close Tabs To The End is enabled"); + + gBrowser.removeTab(testTab); + gBrowser.removeTab(pinned); +}); + diff --git a/browser/base/content/test/general/browser_visibleTabs_tabPreview.js b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js new file mode 100644 index 000000000..7ce4b143f --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* test() { + gPrefService.setBoolPref("browser.ctrlTab.previews", true); + + let [origTab] = gBrowser.visibleTabs; + let tabOne = gBrowser.addTab(); + let tabTwo = gBrowser.addTab(); + + // test the ctrlTab.tabList + pressCtrlTab(); + ok(ctrlTab.tabList.length, 3, "Show 3 tabs in tab preview"); + releaseCtrl(); + + gBrowser.showOnlyTheseTabs([origTab]); + pressCtrlTab(); + ok(ctrlTab.tabList.length, 1, "Show 1 tab in tab preview"); + ok(!ctrlTab.isOpen, "With 1 tab open, Ctrl+Tab doesn't open the preview panel"); + + gBrowser.showOnlyTheseTabs([origTab, tabOne, tabTwo]); + pressCtrlTab(); + ok(ctrlTab.isOpen, "With 3 tabs open, Ctrl+Tab does open the preview panel"); + releaseCtrl(); + + // cleanup + gBrowser.removeTab(tabOne); + gBrowser.removeTab(tabTwo); + + if (gPrefService.prefHasUserValue("browser.ctrlTab.previews")) + gPrefService.clearUserPref("browser.ctrlTab.previews"); +}); + +function pressCtrlTab(aShiftKey) { + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey }); +} + +function releaseCtrl() { + EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }); +} diff --git a/browser/base/content/test/general/browser_web_channel.html b/browser/base/content/test/general/browser_web_channel.html new file mode 100644 index 000000000..f117ccca2 --- /dev/null +++ b/browser/base/content/test/general/browser_web_channel.html @@ -0,0 +1,189 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>web_channel_test</title> +</head> +<body> +<script> + var IFRAME_SRC_ROOT = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_web_channel_iframe.html"; + + window.onload = function() { + var testName = window.location.search.replace(/^\?/, ""); + + switch (testName) { + case "generic": + test_generic(); + break; + case "twoway": + test_twoWay(); + break; + case "multichannel": + test_multichannel(); + break; + case "iframe": + test_iframe(); + break; + case "iframe_pre_redirect": + test_iframe_pre_redirect(); + break; + case "unsolicited": + test_unsolicited(); + break; + case "bubbles": + test_bubbles(); + break; + case "object": + test_object(); + break; + default: + throw new Error(`INVALID TEST NAME ${testName}`); + } + }; + + function test_generic() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "generic", + message: { + something: { + nested: "hello", + }, + } + }) + }); + + window.dispatchEvent(event); + } + + function test_twoWay() { + var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "twoway", + message: { + command: "one", + }, + }) + }); + + window.addEventListener("WebChannelMessageToContent", function(e) { + var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "twoway", + message: { + command: "two", + detail: e.detail.message, + }, + }), + }); + + if (!e.detail.message.error) { + window.dispatchEvent(secondMessage); + } + }, true); + + window.dispatchEvent(firstMessage); + } + + function test_multichannel() { + var event1 = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "wrongchannel", + message: {}, + }) + }); + + var event2 = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "multichannel", + message: {}, + }) + }); + + window.dispatchEvent(event1); + window.dispatchEvent(event2); + } + + function test_iframe() { + // Note that this message is the response to the message sent + // by the iframe! This is bad, as this page is *not* trusted. + window.addEventListener("WebChannelMessageToContent", function(e) { + // the test parent will fail if the echo message is received. + echoEventToChannel(e, "echo"); + }); + + // only attach the iframe for the iframe test to avoid + // interfering with other tests. + var iframe = document.createElement("iframe"); + iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe"); + document.body.appendChild(iframe); + } + + function test_iframe_pre_redirect() { + var iframe = document.createElement("iframe"); + iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe_pre_redirect"); + document.body.appendChild(iframe); + } + + function test_unsolicited() { + // echo any unsolicted events back to chrome. + window.addEventListener("WebChannelMessageToContent", function(e) { + echoEventToChannel(e, "echo"); + }, true); + } + + function test_bubbles() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "not_a_window", + message: { + command: "start" + } + }) + }); + + var nonWindowTarget = document.getElementById("not_a_window"); + + nonWindowTarget.addEventListener("WebChannelMessageToContent", function(e) { + echoEventToChannel(e, "not_a_window"); + }, true); + + + nonWindowTarget.dispatchEvent(event); + } + + function test_object() { + let objectMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: { + id: "objects", + message: { type: "object" } + } + }); + + let stringMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "objects", + message: { type: "string" } + }) + }); + // Test fails if objectMessage is received, we send stringMessage to know + // when we should stop listening for objectMessage + window.dispatchEvent(objectMessage); + window.dispatchEvent(stringMessage); + } + + function echoEventToChannel(e, channelId) { + var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: channelId, + message: e.detail.message, + }) + }); + + e.target.dispatchEvent(echoedEvent); + } +</script> + +<div id="not_a_window"></div> +</body> +</html> diff --git a/browser/base/content/test/general/browser_web_channel.js b/browser/base/content/test/general/browser_web_channel.js new file mode 100644 index 000000000..abc1c6fef --- /dev/null +++ b/browser/base/content/test/general/browser_web_channel.js @@ -0,0 +1,436 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "WebChannel", + "resource://gre/modules/WebChannel.jsm"); + +const HTTP_PATH = "http://example.com"; +const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html"; +const HTTP_MISMATCH_PATH = "http://example.org"; +const HTTP_IFRAME_PATH = "http://mochi.test:8888"; +const HTTP_REDIRECTED_IFRAME_PATH = "http://example.org"; + +requestLongerTimeout(2); // timeouts in debug builds. + +// Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js +// as much as possible. (We only have that since we can't run browser chrome +// tests on Android. Yet?) +var gTests = [ + { + desc: "WebChannel generic message", + run: function* () { + return new Promise(function(resolve, reject) { + let tab; + let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null)); + channel.listen(function (id, message, target) { + is(id, "generic"); + is(message.something.nested, "hello"); + channel.stopListening(); + gBrowser.removeTab(tab); + resolve(); + }); + + tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?generic"); + }); + } + }, + { + desc: "WebChannel generic message in a private window.", + run: function* () { + let promiseTestDone = new Promise(function(resolve, reject) { + let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null)); + channel.listen(function(id, message, target) { + is(id, "generic"); + is(message.something.nested, "hello"); + channel.stopListening(); + resolve(); + }); + }); + + const url = HTTP_PATH + HTTP_ENDPOINT + "?generic"; + let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, url); + yield promiseTestDone; + yield BrowserTestUtils.closeWindow(privateWindow); + } + }, + { + desc: "WebChannel two way communication", + run: function* () { + return new Promise(function(resolve, reject) { + let tab; + let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH, null, null)); + + channel.listen(function (id, message, sender) { + is(id, "twoway", "bad id"); + ok(message.command, "command not ok"); + + if (message.command === "one") { + channel.send({ data: { nested: true } }, sender); + } + + if (message.command === "two") { + is(message.detail.data.nested, true); + channel.stopListening(); + gBrowser.removeTab(tab); + resolve(); + } + }); + + tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?twoway"); + }); + } + }, + { + desc: "WebChannel two way communication in an iframe", + run: function* () { + let parentChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null)); + let iframeChannel = new WebChannel("twoway", Services.io.newURI(HTTP_IFRAME_PATH, null, null)); + let promiseTestDone = new Promise(function (resolve, reject) { + parentChannel.listen(function (id, message, sender) { + reject(new Error("WebChannel message incorrectly sent to parent")); + }); + + iframeChannel.listen(function (id, message, sender) { + is(id, "twoway", "bad id (2)"); + ok(message.command, "command not ok (2)"); + + if (message.command === "one") { + iframeChannel.send({ data: { nested: true } }, sender); + } + + if (message.command === "two") { + is(message.detail.data.nested, true); + resolve(); + } + }); + }); + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?iframe" + }, function* () { + yield promiseTestDone; + parentChannel.stopListening(); + iframeChannel.stopListening(); + }); + } + }, + { + desc: "WebChannel response to a redirected iframe", + run: function* () { + /** + * This test checks that WebChannel responses are only sent + * to an iframe if the iframe has not redirected to another origin. + * Test flow: + * 1. create a page, embed an iframe on origin A. + * 2. the iframe sends a message `redirecting`, then redirects to + * origin B. + * 3. the iframe at origin B is set up to echo any messages back to the + * test parent. + * 4. the test parent receives the `redirecting` message from origin A. + * the test parent creates a new channel with origin B. + * 5. when origin B is ready, it sends a `loaded` message to the test + * parent, letting the test parent know origin B is ready to echo + * messages. + * 5. the test parent tries to send a response to origin A. If the + * WebChannel does not perform a valid origin check, the response + * will be received by origin B. If the WebChannel does perform + * a valid origin check, the response will not be sent. + * 6. the test parent sends a `done` message to origin B, which origin + * B echoes back. If the response to origin A is not echoed but + * the message to origin B is, then hooray, the test passes. + */ + + let preRedirectChannel = new WebChannel("pre_redirect", Services.io.newURI(HTTP_IFRAME_PATH, null, null)); + let postRedirectChannel = new WebChannel("post_redirect", Services.io.newURI(HTTP_REDIRECTED_IFRAME_PATH, null, null)); + + let promiseTestDone = new Promise(function (resolve, reject) { + preRedirectChannel.listen(function (id, message, preRedirectSender) { + if (message.command === "redirecting") { + + postRedirectChannel.listen(function (aId, aMessage, aPostRedirectSender) { + is(aId, "post_redirect"); + isnot(aMessage.command, "no_response_expected"); + + if (aMessage.command === "loaded") { + // The message should not be received on the preRedirectChannel + // because the target window has redirected. + preRedirectChannel.send({ command: "no_response_expected" }, preRedirectSender); + postRedirectChannel.send({ command: "done" }, aPostRedirectSender); + } else if (aMessage.command === "done") { + resolve(); + } else { + reject(new Error(`Unexpected command ${aMessage.command}`)); + } + }); + } else { + reject(new Error(`Unexpected command ${message.command}`)); + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?iframe_pre_redirect" + }, function* () { + yield promiseTestDone; + preRedirectChannel.stopListening(); + postRedirectChannel.stopListening(); + }); + } + }, + { + desc: "WebChannel multichannel", + run: function* () { + return new Promise(function(resolve, reject) { + let tab; + let channel = new WebChannel("multichannel", Services.io.newURI(HTTP_PATH, null, null)); + + channel.listen(function (id, message, sender) { + is(id, "multichannel"); + gBrowser.removeTab(tab); + resolve(); + }); + + tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel"); + }); + } + }, + { + desc: "WebChannel unsolicited send, using system principal", + run: function* () { + let channel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null)); + + // an unsolicted message is sent from Chrome->Content which is then + // echoed back. If the echo is received here, then the content + // received the message. + let messagePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + is(id, "echo"); + is(message.command, "unsolicited"); + + resolve() + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited" + }, function* (targetBrowser) { + channel.send({ command: "unsolicited" }, { + browser: targetBrowser, + principal: Services.scriptSecurityManager.getSystemPrincipal() + }); + yield messagePromise; + channel.stopListening(); + }); + } + }, + { + desc: "WebChannel unsolicited send, using target origin's principal", + run: function* () { + let targetURI = Services.io.newURI(HTTP_PATH, null, null); + let channel = new WebChannel("echo", targetURI); + + // an unsolicted message is sent from Chrome->Content which is then + // echoed back. If the echo is received here, then the content + // received the message. + let messagePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + is(id, "echo"); + is(message.command, "unsolicited"); + + resolve(); + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited" + }, function* (targetBrowser) { + + channel.send({ command: "unsolicited" }, { + browser: targetBrowser, + principal: Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI) + }); + + yield messagePromise; + channel.stopListening(); + }); + } + }, + { + desc: "WebChannel unsolicited send with principal mismatch", + run: function* () { + let targetURI = Services.io.newURI(HTTP_PATH, null, null); + let channel = new WebChannel("echo", targetURI); + + // two unsolicited messages are sent from Chrome->Content. The first, + // `unsolicited_no_response_expected` is sent to the wrong principal + // and should not be echoed back. The second, `done`, is sent to the + // correct principal and should be echoed back. + let messagePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + is(id, "echo"); + + if (message.command === "done") { + resolve(); + } else { + reject(new Error(`Unexpected command ${message.command}`)); + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited" + }, function* (targetBrowser) { + + let mismatchURI = Services.io.newURI(HTTP_MISMATCH_PATH, null, null); + let mismatchPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(mismatchURI); + + // send a message to the wrong principal. It should not be delivered + // to content, and should not be echoed back. + channel.send({ command: "unsolicited_no_response_expected" }, { + browser: targetBrowser, + principal: mismatchPrincipal + }); + + let targetPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI); + + // send the `done` message to the correct principal. It + // should be echoed back. + channel.send({ command: "done" }, { + browser: targetBrowser, + principal: targetPrincipal + }); + + yield messagePromise; + channel.stopListening(); + }); + } + }, + { + desc: "WebChannel non-window target", + run: function* () { + /** + * This test ensures messages can be received from and responses + * sent to non-window elements. + * + * First wait for the non-window element to send a "start" message. + * Then send the non-window element a "done" message. + * The non-window element will echo the "done" message back, if it + * receives the message. + * Listen for the response. If received, good to go! + */ + let channel = new WebChannel("not_a_window", Services.io.newURI(HTTP_PATH, null, null)); + + let testDonePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + if (message.command === "start") { + channel.send({ command: "done" }, sender); + } else if (message.command === "done") { + resolve(); + } else { + reject(new Error(`Unexpected command ${message.command}`)); + } + }); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles" + }, function* () { + yield testDonePromise; + channel.stopListening(); + }); + } + }, + { + desc: "WebChannel disallows non-string message from non-whitelisted origin", + run: function* () { + /** + * This test ensures that non-string messages can't be sent via WebChannels. + * We create a page (on a non-whitelisted origin) which should send us two + * messages immediately. The first message has an object for it's detail, + * and the second has a string. We check that we only get the second + * message. + */ + let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null)); + let testDonePromise = new Promise((resolve, reject) => { + channel.listen((id, message, sender) => { + is(id, "objects"); + is(message.type, "string"); + resolve(); + }); + }); + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?object" + }, function* () { + yield testDonePromise; + channel.stopListening(); + }); + } + }, + { + desc: "WebChannel allows both string and non-string message from whitelisted origin", + run: function* () { + /** + * Same process as above, but we whitelist the origin before loading the page, + * and expect to get *both* messages back (each exactly once). + */ + let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null)); + + let testDonePromise = new Promise((resolve, reject) => { + let sawObject = false; + let sawString = false; + channel.listen((id, message, sender) => { + is(id, "objects"); + if (message.type === "object") { + ok(!sawObject); + sawObject = true; + } else if (message.type === "string") { + ok(!sawString); + sawString = true; + } else { + reject(new Error(`Unknown message type: ${message.type}`)) + } + if (sawObject && sawString) { + resolve(); + } + }); + }); + const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist"; + let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref); + let newWhitelist = origWhitelist + " " + HTTP_PATH; + Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist); + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?object" + }, function* () { + yield testDonePromise; + Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist); + channel.stopListening(); + }); + } + } +]; // gTests + +function test() { + waitForExplicitFinish(); + + Task.spawn(function* () { + for (let testCase of gTests) { + info("Running: " + testCase.desc); + yield testCase.run(); + } + }).then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} diff --git a/browser/base/content/test/general/browser_web_channel_iframe.html b/browser/base/content/test/general/browser_web_channel_iframe.html new file mode 100644 index 000000000..7900e7530 --- /dev/null +++ b/browser/base/content/test/general/browser_web_channel_iframe.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>web_channel_test (iframe)</title> +</head> +<body> +<script> + var REDIRECTED_IFRAME_SRC_ROOT = "http://example.org/browser/browser/base/content/test/general/browser_web_channel_iframe.html"; + + window.onload = function() { + var testName = window.location.search.replace(/^\?/, ""); + switch (testName) { + case "iframe": + test_iframe(); + break; + case "iframe_pre_redirect": + test_iframe_pre_redirect(); + break; + case "iframe_post_redirect": + test_iframe_post_redirect(); + break; + default: + throw new Error(`INVALID TEST NAME ${testName}`); + } + }; + + function test_iframe() { + var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "twoway", + message: { + command: "one", + }, + }) + }); + + window.addEventListener("WebChannelMessageToContent", function(e) { + var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "twoway", + message: { + command: "two", + detail: e.detail.message, + }, + }), + }); + + if (!e.detail.message.error) { + window.dispatchEvent(secondMessage); + } + }, true); + + window.dispatchEvent(firstMessage); + } + + + function test_iframe_pre_redirect() { + var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "pre_redirect", + message: { + command: "redirecting", + }, + }), + }); + window.dispatchEvent(firstMessage); + document.location = REDIRECTED_IFRAME_SRC_ROOT + "?iframe_post_redirect"; + } + + function test_iframe_post_redirect() { + window.addEventListener("WebChannelMessageToContent", function(e) { + var echoMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "post_redirect", + message: e.detail.message, + }), + }); + + window.dispatchEvent(echoMessage); + }, true); + + // Let the test parent know the page has loaded and is ready to echo events + var loadedMessage = new window.CustomEvent("WebChannelMessageToChrome", { + detail: JSON.stringify({ + id: "post_redirect", + message: { + command: "loaded", + }, + }), + }); + window.dispatchEvent(loadedMessage); + } +</script> +</body> +</html> diff --git a/browser/base/content/test/general/browser_windowactivation.js b/browser/base/content/test/general/browser_windowactivation.js new file mode 100644 index 000000000..ae4ba75dc --- /dev/null +++ b/browser/base/content/test/general/browser_windowactivation.js @@ -0,0 +1,183 @@ +/* + * This test checks that window activation state is set properly with multiple tabs. + */ + +var testPage = "data:text/html,<body><style>:-moz-window-inactive { background-color: red; }</style><div id='area'></div></body>"; + +var colorChangeNotifications = 0; +var otherWindow; + +var browser1, browser2; + +function test() { + waitForExplicitFinish(); + waitForFocus(reallyRunTests); +} + +function reallyRunTests() { + + let tab1 = gBrowser.addTab(); + let tab2 = gBrowser.addTab(); + browser1 = gBrowser.getBrowserForTab(tab1); + browser2 = gBrowser.getBrowserForTab(tab2); + + gURLBar.focus(); + + var loadCount = 0; + function check() + { + // wait for both tabs to load + if (++loadCount != 2) { + return; + } + + browser1.removeEventListener("load", check, true); + browser2.removeEventListener("load", check, true); + + sendGetBackgroundRequest(true); + } + + // The test performs four checks, using -moz-window-inactive on two child tabs. + // First, the initial state should be transparent. The second check is done + // while another window is focused. The third check is done after that window + // is closed and the main window focused again. The fourth check is done after + // switching to the second tab. + window.messageManager.addMessageListener("Test:BackgroundColorChanged", function(message) { + colorChangeNotifications++; + + switch (colorChangeNotifications) { + case 1: + is(message.data.color, "transparent", "first window initial"); + break; + case 2: + is(message.data.color, "transparent", "second window initial"); + runOtherWindowTests(); + break; + case 3: + is(message.data.color, "rgb(255, 0, 0)", "first window lowered"); + break; + case 4: + is(message.data.color, "rgb(255, 0, 0)", "second window lowered"); + sendGetBackgroundRequest(true); + otherWindow.close(); + break; + case 5: + is(message.data.color, "transparent", "first window raised"); + break; + case 6: + is(message.data.color, "transparent", "second window raised"); + gBrowser.selectedTab = tab2; + break; + case 7: + is(message.data.color, "transparent", "first window after tab switch"); + break; + case 8: + is(message.data.color, "transparent", "second window after tab switch"); + finishTest(); + break; + case 9: + ok(false, "too many color change notifications"); + break; + } + }); + + window.messageManager.addMessageListener("Test:FocusReceived", function(message) { + // No color change should occur after a tab switch. + if (colorChangeNotifications == 6) { + sendGetBackgroundRequest(false); + } + }); + + window.messageManager.addMessageListener("Test:ActivateEvent", function(message) { + ok(message.data.ok, "Test:ActivateEvent"); + }); + + window.messageManager.addMessageListener("Test:DeactivateEvent", function(message) { + ok(message.data.ok, "Test:DeactivateEvent"); + }); + + browser1.addEventListener("load", check, true); + browser2.addEventListener("load", check, true); + browser1.contentWindow.location = testPage; + browser2.contentWindow.location = testPage; + + browser1.messageManager.loadFrameScript("data:,(" + childFunction.toString() + ")();", true); + browser2.messageManager.loadFrameScript("data:,(" + childFunction.toString() + ")();", true); + + gBrowser.selectedTab = tab1; +} + +function sendGetBackgroundRequest(ifChanged) +{ + browser1.messageManager.sendAsyncMessage("Test:GetBackgroundColor", { ifChanged: ifChanged }); + browser2.messageManager.sendAsyncMessage("Test:GetBackgroundColor", { ifChanged: ifChanged }); +} + +function runOtherWindowTests() { + otherWindow = window.open("data:text/html,<body>Hi</body>", "", "chrome"); + waitForFocus(function () { + sendGetBackgroundRequest(true); + }, otherWindow); +} + +function finishTest() +{ + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); + otherWindow = null; + finish(); +} + +function childFunction() +{ + let oldColor = null; + + let expectingResponse = false; + let ifChanged = true; + + addMessageListener("Test:GetBackgroundColor", function(message) { + expectingResponse = true; + ifChanged = message.data.ifChanged; + }); + + content.addEventListener("focus", function () { + sendAsyncMessage("Test:FocusReceived", { }); + }, false); + + var windowGotActivate = false; + var windowGotDeactivate = false; + addEventListener("activate", function() { + sendAsyncMessage("Test:ActivateEvent", { ok: !windowGotActivate }); + windowGotActivate = false; + }); + + addEventListener("deactivate", function() { + sendAsyncMessage("Test:DeactivateEvent", { ok: !windowGotDeactivate }); + windowGotDeactivate = false; + }); + content.addEventListener("activate", function() { + windowGotActivate = true; + }); + + content.addEventListener("deactivate", function() { + windowGotDeactivate = true; + }); + + content.setInterval(function () { + if (!expectingResponse) { + return; + } + + let area = content.document.getElementById("area"); + if (!area) { + return; /* hasn't loaded yet */ + } + + let color = content.getComputedStyle(area, "").backgroundColor; + if (oldColor != color || !ifChanged) { + expectingResponse = false; + oldColor = color; + sendAsyncMessage("Test:BackgroundColorChanged", { color: color }); + } + }, 20); +} diff --git a/browser/base/content/test/general/browser_windowopen_reflows.js b/browser/base/content/test/general/browser_windowopen_reflows.js new file mode 100644 index 000000000..7dac8aad6 --- /dev/null +++ b/browser/base/content/test/general/browser_windowopen_reflows.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const EXPECTED_REFLOWS = [ + // handleEvent flushes layout to get the tabstrip width after a resize. + "handleEvent@chrome://browser/content/tabbrowser.xml|", + + // Loading a tab causes a reflow. + "loadTabs@chrome://browser/content/tabbrowser.xml|" + + "loadOneOrMoreURIs@chrome://browser/content/browser.js|" + + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|", + + // Selecting the address bar causes a reflow. + "select@chrome://global/content/bindings/textbox.xml|" + + "focusAndSelectUrlBar@chrome://browser/content/browser.js|" + + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|", + + // Focusing the content area causes a reflow. + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|", + + // Sometimes sessionstore collects data during this test, which causes a sync reflow + // (https://bugzilla.mozilla.org/show_bug.cgi?id=892154 will fix this) + "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm", +]; + +if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") { + // TabsInTitlebar._update causes a reflow on OS X and Windows trying to do calculations + // since layout info is already dirty. This doesn't seem to happen before + // MozAfterPaint on Linux. + EXPECTED_REFLOWS.push("TabsInTitlebar._update/rect@chrome://browser/content/browser-tabsintitlebar.js|" + + "TabsInTitlebar._update@chrome://browser/content/browser-tabsintitlebar.js|" + + "updateAppearance@chrome://browser/content/browser-tabsintitlebar.js|" + + "handleEvent@chrome://browser/content/tabbrowser.xml|"); +} + +if (Services.appinfo.OS == "Darwin") { + // _onOverflow causes a reflow getting widths. + EXPECTED_REFLOWS.push("OverflowableToolbar.prototype._onOverflow@resource:///modules/CustomizableUI.jsm|" + + "OverflowableToolbar.prototype.init@resource:///modules/CustomizableUI.jsm|" + + "OverflowableToolbar.prototype.observe@resource:///modules/CustomizableUI.jsm|" + + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|"); + // Same as above since in packaged builds there are no function names and the resource URI includes "app" + EXPECTED_REFLOWS.push("@resource://app/modules/CustomizableUI.jsm|" + + "@resource://app/modules/CustomizableUI.jsm|" + + "@resource://app/modules/CustomizableUI.jsm|" + + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|"); +} + +/* + * This test ensures that there are no unexpected + * uninterruptible reflows when opening new windows. + */ +function test() { + waitForExplicitFinish(); + + // Add a reflow observer and open a new window + let win = OpenBrowserWindow(); + let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + docShell.addWeakReflowObserver(observer); + + // Wait until the mozafterpaint event occurs. + waitForMozAfterPaint(win, function paintListener() { + // Remove reflow observer and clean up. + docShell.removeWeakReflowObserver(observer); + win.close(); + + finish(); + }); +} + +var observer = { + reflow: function (start, end) { + // Gather information about the current code path. + let stack = new Error().stack; + let path = stack.split("\n").slice(1).map(line => { + return line.replace(/:\d+:\d+$/, ""); + }).join("|"); + let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|"); + + // Stack trace is empty. Reflow was triggered by native code. + if (path === "") { + return; + } + + // Check if this is an expected reflow. + for (let expectedStack of EXPECTED_REFLOWS) { + if (path.startsWith(expectedStack) || + // Accept an empty function name for gBrowserInit._delayedStartup or TabsInTitlebar._update to workaround bug 906578. + path.startsWith(expectedStack.replace(/(^|\|)(gBrowserInit\._delayedStartup|TabsInTitlebar\._update)@/, "$1@"))) { + ok(true, "expected uninterruptible reflow '" + expectedStack + "'"); + return; + } + } + + ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'"); + }, + + reflowInterruptible: function (start, end) { + // We're not interested in interruptible reflows. + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, + Ci.nsISupportsWeakReference]) +}; + +function waitForMozAfterPaint(win, callback) { + win.addEventListener("MozAfterPaint", function onEnd(event) { + if (event.target != win) + return; + win.removeEventListener("MozAfterPaint", onEnd); + executeSoon(callback); + }); +} diff --git a/browser/base/content/test/general/browser_zbug569342.js b/browser/base/content/test/general/browser_zbug569342.js new file mode 100644 index 000000000..2dac5acde --- /dev/null +++ b/browser/base/content/test/general/browser_zbug569342.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var gTab = null; + +function load(url, cb) { + gTab = gBrowser.addTab(url); + gBrowser.addEventListener("load", function (event) { + if (event.target.location != url) + return; + + gBrowser.removeEventListener("load", arguments.callee, true); + // Trigger onLocationChange by switching tabs. + gBrowser.selectedTab = gTab; + cb(); + }, true); +} + +function test() { + waitForExplicitFinish(); + + ok(gFindBar.hidden, "Find bar should not be visible by default"); + + // Open the Find bar before we navigate to pages that shouldn't have it. + EventUtils.synthesizeKey("f", { accelKey: true }); + ok(!gFindBar.hidden, "Find bar should be visible"); + + nextTest(); +} + +var urls = [ + "about:config", + "about:addons", +]; + +function nextTest() { + let url = urls.shift(); + if (url) { + testFindDisabled(url, nextTest); + } else { + // Make sure the find bar is re-enabled after disabled page is closed. + testFindEnabled("about:blank", function () { + EventUtils.synthesizeKey("VK_ESCAPE", { }); + ok(gFindBar.hidden, "Find bar should now be hidden"); + finish(); + }); + } +} + +function testFindDisabled(url, cb) { + load(url, function() { + ok(gFindBar.hidden, "Find bar should not be visible"); + EventUtils.synthesizeKey("/", {}, gTab.linkedBrowser.contentWindow); + ok(gFindBar.hidden, "Find bar should not be visible"); + EventUtils.synthesizeKey("f", { accelKey: true }); + ok(gFindBar.hidden, "Find bar should not be visible"); + ok(document.getElementById("cmd_find").getAttribute("disabled"), + "Find command should be disabled"); + + gBrowser.removeTab(gTab); + cb(); + }); +} + +function testFindEnabled(url, cb) { + load(url, function() { + ok(!document.getElementById("cmd_find").getAttribute("disabled"), + "Find command should not be disabled"); + + // Open Find bar and then close it. + EventUtils.synthesizeKey("f", { accelKey: true }); + ok(!gFindBar.hidden, "Find bar should be visible again"); + EventUtils.synthesizeKey("VK_ESCAPE", { }); + ok(gFindBar.hidden, "Find bar should now be hidden"); + + gBrowser.removeTab(gTab); + cb(); + }); +} diff --git a/browser/base/content/test/general/bug1262648_string_with_newlines.dtd b/browser/base/content/test/general/bug1262648_string_with_newlines.dtd new file mode 100644 index 000000000..308072c4e --- /dev/null +++ b/browser/base/content/test/general/bug1262648_string_with_newlines.dtd @@ -0,0 +1,3 @@ +<!ENTITY foo.bar "This string +contains +newlines!">
\ No newline at end of file diff --git a/browser/base/content/test/general/bug364677-data.xml b/browser/base/content/test/general/bug364677-data.xml new file mode 100644 index 000000000..b48915c05 --- /dev/null +++ b/browser/base/content/test/general/bug364677-data.xml @@ -0,0 +1,5 @@ +<rss version="2.0"> + <channel> + <title>t</title> + </channel> +</rss> diff --git a/browser/base/content/test/general/bug364677-data.xml^headers^ b/browser/base/content/test/general/bug364677-data.xml^headers^ new file mode 100644 index 000000000..f203c6368 --- /dev/null +++ b/browser/base/content/test/general/bug364677-data.xml^headers^ @@ -0,0 +1 @@ +Content-Type: text/xml diff --git a/browser/base/content/test/general/bug395533-data.txt b/browser/base/content/test/general/bug395533-data.txt new file mode 100644 index 000000000..e0ed39850 --- /dev/null +++ b/browser/base/content/test/general/bug395533-data.txt @@ -0,0 +1,6 @@ +<rss version="2.0"> + <channel> + <link>http://example.org/</link> + <title>t</title> + </channel> +</rss> diff --git a/browser/base/content/test/general/bug592338.html b/browser/base/content/test/general/bug592338.html new file mode 100644 index 000000000..159b21a76 --- /dev/null +++ b/browser/base/content/test/general/bug592338.html @@ -0,0 +1,24 @@ +<html> +<head> +<script type="text/javascript"> +var theme = { + id: "test", + name: "Test Background", + headerURL: "http://example.com/firefox/personas/01/header.jpg", + footerURL: "http://example.com/firefox/personas/01/footer.jpg", + textcolor: "#fff", + accentcolor: "#6b6b6b" +}; + +function setTheme(node) { + node.setAttribute("data-browsertheme", JSON.stringify(theme)); + var event = document.createEvent("Events"); + event.initEvent("InstallBrowserTheme", true, false); + node.dispatchEvent(event); +} +</script> +</head> +<body> +<a id="theme-install" href="#" onclick="setTheme(this)">Install</a> +</body> +</html> diff --git a/browser/base/content/test/general/bug792517-2.html b/browser/base/content/test/general/bug792517-2.html new file mode 100644 index 000000000..bfc24d817 --- /dev/null +++ b/browser/base/content/test/general/bug792517-2.html @@ -0,0 +1,5 @@ +<html> +<body> +<a href="bug792517.sjs" id="fff">this is a link</a> +</body> +</html> diff --git a/browser/base/content/test/general/bug792517.html b/browser/base/content/test/general/bug792517.html new file mode 100644 index 000000000..e7c040bf1 --- /dev/null +++ b/browser/base/content/test/general/bug792517.html @@ -0,0 +1,5 @@ +<html> +<body> +<img src="moz.png" id="img"> +</body> +</html> diff --git a/browser/base/content/test/general/bug792517.sjs b/browser/base/content/test/general/bug792517.sjs new file mode 100644 index 000000000..91e5aa23f --- /dev/null +++ b/browser/base/content/test/general/bug792517.sjs @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + if (aRequest.hasHeader('Cookie')) { + aResponse.write("cookie-present"); + } else { + aResponse.setHeader("Set-Cookie", "foopy=1"); + aResponse.write("cookie-not-present"); + } +} diff --git a/browser/base/content/test/general/bug839103.css b/browser/base/content/test/general/bug839103.css new file mode 100644 index 000000000..611907d3d --- /dev/null +++ b/browser/base/content/test/general/bug839103.css @@ -0,0 +1 @@ +* {} diff --git a/browser/base/content/test/general/clipboard_pastefile.html b/browser/base/content/test/general/clipboard_pastefile.html new file mode 100644 index 000000000..fcbf60ed2 --- /dev/null +++ b/browser/base/content/test/general/clipboard_pastefile.html @@ -0,0 +1,37 @@ +<html><body> +<script> +function checkPaste(event) +{ + let output = document.getElementById("output"); + output.textContent = checkPasteHelper(event); +} + +function checkPasteHelper(event) +{ + let dt = event.clipboardData; + if (dt.types.length != 2) + return "Wrong number of types; got " + dt.types.length; + + for (let type of dt.types) { + if (type != "Files" && type != "application/x-moz-file") + return "Invalid type for types; got" + type; + } + + for (let type of dt.mozTypesAt(0)) { + if (type != "Files" && type != "application/x-moz-file") + return "Invalid type for mozTypesAt; got" + type; + } + + if (dt.getData("text/plain")) + return "text/plain found with getData"; + if (dt.mozGetDataAt("text/plain", 0)) + return "text/plain found with mozGetDataAt"; + + return "Passed"; +} +</script> + +<input id="input" onpaste="checkPaste(event)"> +<div id="output"></div> + +</body></html> diff --git a/browser/base/content/test/general/close_beforeunload.html b/browser/base/content/test/general/close_beforeunload.html new file mode 100644 index 000000000..4b62002cc --- /dev/null +++ b/browser/base/content/test/general/close_beforeunload.html @@ -0,0 +1,8 @@ +<body> + <p>I will close myself if you close me.</p> + <script> + window.onbeforeunload = function() { + window.close(); + }; + </script> +</body> diff --git a/browser/base/content/test/general/close_beforeunload_opens_second_tab.html b/browser/base/content/test/general/close_beforeunload_opens_second_tab.html new file mode 100644 index 000000000..243307a0e --- /dev/null +++ b/browser/base/content/test/general/close_beforeunload_opens_second_tab.html @@ -0,0 +1,3 @@ +<body>
+ <a href="#" onclick="window.open('close_beforeunload.html', '_blank')">Open second tab</a>
+</body>
diff --git a/browser/base/content/test/general/contentSearchUI.html b/browser/base/content/test/general/contentSearchUI.html new file mode 100644 index 000000000..3750ac2b0 --- /dev/null +++ b/browser/base/content/test/general/contentSearchUI.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> + +<html> +<head> +<meta charset="utf-8"> +<script type="application/javascript;version=1.8" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"> +</script> +<script type="application/javascript;version=1.8" + src="chrome://browser/content/contentSearchUI.js"> +</script> +<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/> +</head> +<body> + +<div id="container" style="position: relative;"><input type="text" value=""/></div> + +</body> +</html> diff --git a/browser/base/content/test/general/contentSearchUI.js b/browser/base/content/test/general/contentSearchUI.js new file mode 100644 index 000000000..0e46230a2 --- /dev/null +++ b/browser/base/content/test/general/contentSearchUI.js @@ -0,0 +1,209 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +(function () { + +const TEST_MSG = "ContentSearchUIControllerTest"; +const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml"; +var gController; + +addMessageListener(TEST_MSG, msg => { + messageHandlers[msg.data.type](msg.data.data); +}); + +var messageHandlers = { + + init: function() { + Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME); + let input = content.document.querySelector("input"); + gController = + new content.ContentSearchUIController(input, input.parentNode, "test", "test"); + content.addEventListener("ContentSearchService", function listener(aEvent) { + if (aEvent.detail.type == "State" && + gController.defaultEngine.name == ENGINE_NAME) { + content.removeEventListener("ContentSearchService", listener); + ack("init"); + } + }); + gController.remoteTimeout = 5000; + }, + + key: function (arg) { + let keyName = typeof(arg) == "string" ? arg : arg.key; + content.synthesizeKey(keyName, arg.modifiers || {}); + let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb(); + wait(ack.bind(null, "key")); + }, + + startComposition: function (arg) { + content.synthesizeComposition({ type: "compositionstart", data: "" }); + ack("startComposition"); + }, + + changeComposition: function (arg) { + let data = typeof(arg) == "string" ? arg : arg.data; + content.synthesizeCompositionChange({ + composition: { + string: data, + clauses: [ + { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + caret: { start: data.length, length: 0 } + }); + let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb(); + wait(ack.bind(null, "changeComposition")); + }, + + commitComposition: function () { + content.synthesizeComposition({ type: "compositioncommitasis" }); + ack("commitComposition"); + }, + + focus: function () { + gController.input.focus(); + ack("focus"); + }, + + blur: function () { + gController.input.blur(); + ack("blur"); + }, + + waitForSearch: function () { + waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData)); + }, + + waitForSearchSettings: function () { + waitForContentSearchEvent("ManageEngines", + aData => ack("waitForSearchSettings", aData)); + }, + + mousemove: function (itemIndex) { + let row; + if (itemIndex == -1) { + row = gController._table.firstChild; + } + else { + let allElts = [...gController._suggestionsList.children, + ...gController._oneOffButtons, + content.document.getElementById("contentSearchSettingsButton")]; + row = allElts[itemIndex]; + } + let event = { + type: "mousemove", + clickcount: 0, + } + row.addEventListener("mousemove", function handler() { + row.removeEventListener("mousemove", handler); + ack("mousemove"); + }); + content.synthesizeMouseAtCenter(row, event); + }, + + click: function (arg) { + let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg; + let row; + if (eltIdx == -1) { + row = gController._table.firstChild; + } + else { + let allElts = [...gController._suggestionsList.children, + ...gController._oneOffButtons, + content.document.getElementById("contentSearchSettingsButton")]; + row = allElts[eltIdx]; + } + let event = arg.modifiers || {}; + // synthesizeMouseAtCenter defaults to sending a mousedown followed by a + // mouseup if the event type is not specified. + content.synthesizeMouseAtCenter(row, event); + ack("click"); + }, + + addInputValueToFormHistory: function () { + gController.addInputValueToFormHistory(); + ack("addInputValueToFormHistory"); + }, + + addDuplicateOneOff: function () { + let btn = gController._oneOffButtons[gController._oneOffButtons.length - 1]; + let newBtn = btn.cloneNode(true); + btn.parentNode.appendChild(newBtn); + gController._oneOffButtons.push(newBtn); + ack("addDuplicateOneOff"); + }, + + removeLastOneOff: function () { + gController._oneOffButtons.pop().remove(); + ack("removeLastOneOff"); + }, + + reset: function () { + // Reset both the input and suggestions by select all + delete. If there was + // no text entered, this won't have any effect, so also escape to ensure the + // suggestions table is closed. + gController.input.focus(); + content.synthesizeKey("a", { accelKey: true }); + content.synthesizeKey("VK_DELETE", {}); + content.synthesizeKey("VK_ESCAPE", {}); + ack("reset"); + }, +}; + +function ack(aType, aData) { + sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() }); +} + +function waitForSuggestions(cb) { + let observer = new content.MutationObserver(() => { + if (gController.input.getAttribute("aria-expanded") == "true") { + observer.disconnect(); + cb(); + } + }); + observer.observe(gController.input, { + attributes: true, + attributeFilter: ["aria-expanded"], + }); +} + +function waitForContentSearchEvent(messageType, cb) { + let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"]. + getService(content.SpecialPowers.Ci.nsIMessageListenerManager); + mm.addMessageListener("ContentSearch", function listener(aMsg) { + if (aMsg.data.type != messageType) { + return; + } + mm.removeMessageListener("ContentSearch", listener); + cb(aMsg.data.data); + }); +} + +function currentState() { + let state = { + selectedIndex: gController.selectedIndex, + selectedButtonIndex: gController.selectedButtonIndex, + numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions, + suggestionAtIndex: [], + isFormHistorySuggestionAtIndex: [], + + tableHidden: gController._table.hidden, + + inputValue: gController.input.value, + ariaExpanded: gController.input.getAttribute("aria-expanded"), + }; + + if (state.numSuggestions) { + for (let i = 0; i < gController.numSuggestions; i++) { + state.suggestionAtIndex.push(gController.suggestionAtIndex(i)); + state.isFormHistorySuggestionAtIndex.push( + gController.isFormHistorySuggestionAtIndex(i)); + } + } + + return state; +} + +})(); diff --git a/browser/base/content/test/general/content_aboutAccounts.js b/browser/base/content/test/general/content_aboutAccounts.js new file mode 100644 index 000000000..12ac04934 --- /dev/null +++ b/browser/base/content/test/general/content_aboutAccounts.js @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file is loaded as a "content script" for browser_aboutAccounts tests +"use strict"; + +var {interfaces: Ci, utils: Cu} = Components; + +addEventListener("load", function load(event) { + if (event.target != content.document) { + return; + } +// content.document.removeEventListener("load", load, true); + sendAsyncMessage("test:document:load"); + // Opening Sync prefs in tests is a pain as leaks are reported due to the + // in-flight promises. For now we just mock the openPrefs() function and have + // it send a message back to the test so we know it was called. + content.openPrefs = function() { + sendAsyncMessage("test:openPrefsCalled"); + } +}, true); + +addEventListener("DOMContentLoaded", function domContentLoaded(event) { + removeEventListener("DOMContentLoaded", domContentLoaded, true); + let iframe = content.document.getElementById("remote"); + if (!iframe) { + // at least one test initially loads about:blank - in that case, we are done. + return; + } + // We use DOMContentLoaded here as that fires for our iframe even when we've + // arranged for the URL in the iframe to cause an error. + addEventListener("DOMContentLoaded", function iframeLoaded(dclEvent) { + if (iframe.contentWindow.location.href == "about:blank" || + dclEvent.target != iframe.contentDocument) { + return; + } + removeEventListener("DOMContentLoaded", iframeLoaded, true); + sendAsyncMessage("test:iframe:load", {url: iframe.contentDocument.location.href}); + // And an event listener for the test responses, which we send to the test + // via a message. + iframe.contentWindow.addEventListener("FirefoxAccountsTestResponse", function (fxAccountsEvent) { + sendAsyncMessage("test:response", {data: fxAccountsEvent.detail.data}); + }, true); + }, true); +}, true); + +// Return the visibility state of a list of ids. +addMessageListener("test:check-visibilities", function (message) { + let result = {}; + for (let id of message.data.ids) { + let elt = content.document.getElementById(id); + if (elt) { + let displayStyle = content.window.getComputedStyle(elt).display; + if (displayStyle == 'none') { + result[id] = false; + } else if (displayStyle == 'block') { + result[id] = true; + } else { + result[id] = "strange: " + displayStyle; // tests should fail! + } + } else { + result[id] = "doesn't exist: " + id; + } + } + sendAsyncMessage("test:check-visibilities-response", result); +}); + +addMessageListener("test:load-with-mocked-profile-path", function (message) { + addEventListener("DOMContentLoaded", function domContentLoaded(event) { + removeEventListener("DOMContentLoaded", domContentLoaded, true); + content.getDefaultProfilePath = () => message.data.profilePath; + // now wait for the iframe to load. + let iframe = content.document.getElementById("remote"); + iframe.addEventListener("load", function iframeLoaded(loadEvent) { + if (iframe.contentWindow.location.href == "about:blank" || + loadEvent.target != iframe) { + return; + } + iframe.removeEventListener("load", iframeLoaded, true); + sendAsyncMessage("test:load-with-mocked-profile-path-response", + {url: iframe.contentDocument.location.href}); + }, true); + }); + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + webNav.loadURI(message.data.url, webNav.LOAD_FLAGS_NONE, null, null, null); +}, true); diff --git a/browser/base/content/test/general/contextmenu_common.js b/browser/base/content/test/general/contextmenu_common.js new file mode 100644 index 000000000..1a0fa931a --- /dev/null +++ b/browser/base/content/test/general/contextmenu_common.js @@ -0,0 +1,324 @@ +var lastElement; + +function openContextMenuFor(element, shiftkey, waitForSpellCheck) { + // Context menu should be closed before we open it again. + is(SpecialPowers.wrap(contextMenu).state, "closed", "checking if popup is closed"); + + if (lastElement) + lastElement.blur(); + element.focus(); + + // Some elements need time to focus and spellcheck before any tests are + // run on them. + function actuallyOpenContextMenuFor() { + lastElement = element; + var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey }; + synthesizeMouse(element, 2, 2, eventDetails, element.ownerGlobal); + } + + if (waitForSpellCheck) { + var { onSpellCheck } = SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {}); + onSpellCheck(element, actuallyOpenContextMenuFor); + } + else { + actuallyOpenContextMenuFor(); + } +} + +function closeContextMenu() { + contextMenu.hidePopup(); +} + +function getVisibleMenuItems(aMenu, aData) { + var items = []; + var accessKeys = {}; + for (var i = 0; i < aMenu.childNodes.length; i++) { + var item = aMenu.childNodes[i]; + if (item.hidden) + continue; + + var key = item.accessKey; + if (key) + key = key.toLowerCase(); + + var isPageMenuItem = item.hasAttribute("generateditemid"); + + if (item.nodeName == "menuitem") { + var isGenerated = item.className == "spell-suggestion" + || item.className == "sendtab-target"; + if (isGenerated) { + is(item.id, "", "child menuitem #" + i + " is generated"); + } else if (isPageMenuItem) { + is(item.id, "", "child menuitem #" + i + " is a generated page menu item"); + } else { + ok(item.id, "child menuitem #" + i + " has an ID"); + } + var label = item.getAttribute("label"); + ok(label.length, "menuitem " + item.id + " has a label"); + if (isGenerated) { + is(key, "", "Generated items shouldn't have an access key"); + items.push("*" + label); + } else if (isPageMenuItem) { + items.push("+" + label); + } else if (item.id.indexOf("spell-check-dictionary-") != 0 && + item.id != "spell-no-suggestions" && + item.id != "spell-add-dictionaries-main" && + item.id != "context-savelinktopocket" && + item.id != "fill-login-saved-passwords" && + item.id != "fill-login-no-logins") { + ok(key, "menuitem " + item.id + " has an access key"); + if (accessKeys[key]) + ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]); + else + accessKeys[key] = item.id; + } + if (!isGenerated && !isPageMenuItem) { + items.push(item.id); + } + if (isPageMenuItem) { + var p = {}; + p.type = item.getAttribute("type"); + p.icon = item.getAttribute("image"); + p.checked = item.hasAttribute("checked"); + p.disabled = item.hasAttribute("disabled"); + items.push(p); + } else { + items.push(!item.disabled); + } + } else if (item.nodeName == "menuseparator") { + ok(true, "--- seperator id is " + item.id); + items.push("---"); + items.push(null); + } else if (item.nodeName == "menu") { + if (isPageMenuItem) { + item.id = "generated-submenu-" + aData.generatedSubmenuId++; + } + ok(item.id, "child menu #" + i + " has an ID"); + if (!isPageMenuItem) { + ok(key, "menu has an access key"); + if (accessKeys[key]) + ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]); + else + accessKeys[key] = item.id; + } + items.push(item.id); + items.push(!item.disabled); + // Add a dummy item so that the indexes in checkMenu are the same + // for expectedItems and actualItems. + items.push([]); + items.push(null); + } else if (item.nodeName == "menugroup") { + ok(item.id, "child menugroup #" + i + " has an ID"); + items.push(item.id); + items.push(!item.disabled); + var menugroupChildren = []; + for (var child of item.children) { + if (child.hidden) + continue; + + menugroupChildren.push([child.id, !child.disabled]); + } + items.push(menugroupChildren); + items.push(null); + } else { + ok(false, "child #" + i + " of menu ID " + aMenu.id + + " has an unknown type (" + item.nodeName + ")"); + } + } + return items; +} + +function checkContextMenu(expectedItems) { + is(contextMenu.state, "open", "checking if popup is open"); + var data = { generatedSubmenuId: 1 }; + checkMenu(contextMenu, expectedItems, data); +} + +function checkMenuItem(actualItem, actualEnabled, expectedItem, expectedEnabled, index) { + is(actualItem, expectedItem, + "checking item #" + index/2 + " (" + expectedItem + ") name"); + + if (typeof expectedEnabled == "object" && expectedEnabled != null || + typeof actualEnabled == "object" && actualEnabled != null) { + + ok(!(actualEnabled == null), "actualEnabled is not null"); + ok(!(expectedEnabled == null), "expectedEnabled is not null"); + is(typeof actualEnabled, typeof expectedEnabled, "checking types"); + + if (typeof actualEnabled != typeof expectedEnabled || + actualEnabled == null || expectedEnabled == null) + return; + + is(actualEnabled.type, expectedEnabled.type, + "checking item #" + index/2 + " (" + expectedItem + ") type attr value"); + var icon = actualEnabled.icon; + if (icon) { + var tmp = ""; + var j = icon.length - 1; + while (j && icon[j] != "/") { + tmp = icon[j--] + tmp; + } + icon = tmp; + } + is(icon, expectedEnabled.icon, + "checking item #" + index/2 + " (" + expectedItem + ") icon attr value"); + is(actualEnabled.checked, expectedEnabled.checked, + "checking item #" + index/2 + " (" + expectedItem + ") has checked attr"); + is(actualEnabled.disabled, expectedEnabled.disabled, + "checking item #" + index/2 + " (" + expectedItem + ") has disabled attr"); + } else if (expectedEnabled != null) + is(actualEnabled, expectedEnabled, + "checking item #" + index/2 + " (" + expectedItem + ") enabled state"); +} + +/* + * checkMenu - checks to see if the specified <menupopup> contains the + * expected items and state. + * expectedItems is a array of (1) item IDs and (2) a boolean specifying if + * the item is enabled or not (or null to ignore it). Submenus can be checked + * by providing a nested array entry after the expected <menu> ID. + * For example: ["blah", true, // item enabled + * "submenu", null, // submenu + * ["sub1", true, // submenu contents + * "sub2", false], null, // submenu contents + * "lol", false] // item disabled + * + */ +function checkMenu(menu, expectedItems, data) { + var actualItems = getVisibleMenuItems(menu, data); + // ok(false, "Items are: " + actualItems); + for (var i = 0; i < expectedItems.length; i+=2) { + var actualItem = actualItems[i]; + var actualEnabled = actualItems[i + 1]; + var expectedItem = expectedItems[i]; + var expectedEnabled = expectedItems[i + 1]; + if (expectedItem instanceof Array) { + ok(true, "Checking submenu/menugroup..."); + var previousId = expectedItems[i - 2]; // The last item was the menu ID. + var previousItem = menu.getElementsByAttribute("id", previousId)[0]; + ok(previousItem, (previousItem ? previousItem.nodeName : "item") + " with previous id (" + previousId + ") found"); + if (previousItem && previousItem.nodeName == "menu") { + ok(previousItem, "got a submenu element of id='" + previousId + "'"); + is(previousItem.nodeName, "menu", "submenu element of id='" + previousId + + "' has expected nodeName"); + checkMenu(previousItem.menupopup, expectedItem, data, i); + } else if (previousItem && previousItem.nodeName == "menugroup") { + ok(expectedItem.length, "menugroup must not be empty"); + for (var j = 0; j < expectedItem.length / 2; j++) { + checkMenuItem(actualItems[i][j][0], actualItems[i][j][1], expectedItem[j*2], expectedItem[j*2+1], i+j*2); + } + i += j; + } else { + ok(false, "previous item is not a menu or menugroup"); + } + } else { + checkMenuItem(actualItem, actualEnabled, expectedItem, expectedEnabled, i); + } + } + // Could find unexpected extra items at the end... + is(actualItems.length, expectedItems.length, "checking expected number of menu entries"); +} + +let lastElementSelector = null; +/** + * Right-clicks on the element that matches `selector` and checks the + * context menu that appears against the `menuItems` array. + * + * @param {String} selector + * A selector passed to querySelector to find + * the element that will be referenced. + * @param {Array} menuItems + * An array of menuitem ids and their associated enabled state. A state + * of null means that it will be ignored. Ids of '---' are used for + * menuseparators. + * @param {Object} options, optional + * skipFocusChange: don't move focus to the element before test, useful + * if you want to delay spell-check initialization + * offsetX: horizontal mouse offset from the top-left corner of + * the element, optional + * offsetY: vertical mouse offset from the top-left corner of the + * element, optional + * centered: if true, mouse position is centered in element, defaults + * to true if offsetX and offsetY are not provided + * waitForSpellCheck: wait until spellcheck is initialized before + * starting test + * preCheckContextMenuFn: callback to run before opening menu + * onContextMenuShown: callback to run when the context menu is shown + * postCheckContextMenuFn: callback to run after opening menu + * @return {Promise} resolved after the test finishes + */ +function* test_contextmenu(selector, menuItems, options={}) { + contextMenu = document.getElementById("contentAreaContextMenu"); + is(contextMenu.state, "closed", "checking if popup is closed"); + + // Default to centered if no positioning is defined. + if (!options.offsetX && !options.offsetY) { + options.centered = true; + } + + if (!options.skipFocusChange) { + yield ContentTask.spawn(gBrowser.selectedBrowser, + [lastElementSelector, selector], + function*([contentLastElementSelector, contentSelector]) { + if (contentLastElementSelector) { + let contentLastElement = content.document.querySelector(contentLastElementSelector); + contentLastElement.blur(); + } + let element = content.document.querySelector(contentSelector); + element.focus(); + }); + lastElementSelector = selector; + info(`Moved focus to ${selector}`); + } + + if (options.preCheckContextMenuFn) { + yield options.preCheckContextMenuFn(); + info("Completed preCheckContextMenuFn"); + } + + if (options.waitForSpellCheck) { + info("Waiting for spell check"); + yield ContentTask.spawn(gBrowser.selectedBrowser, selector, function*(contentSelector) { + let {onSpellCheck} = Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {}); + let element = content.document.querySelector(contentSelector); + yield new Promise(resolve => onSpellCheck(element, resolve)); + info("Spell check running"); + }); + } + + let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + yield BrowserTestUtils.synthesizeMouse(selector, options.offsetX || 0, options.offsetY || 0, { + type: "contextmenu", + button: 2, + shiftkey: options.shiftkey, + centered: options.centered + }, + gBrowser.selectedBrowser); + yield awaitPopupShown; + info("Popup Shown"); + + if (options.onContextMenuShown) { + yield options.onContextMenuShown(); + info("Completed onContextMenuShown"); + } + + if (menuItems) { + if (Services.prefs.getBoolPref("devtools.inspector.enabled")) { + let inspectItems = ["---", null, + "context-inspect", true]; + menuItems = menuItems.concat(inspectItems); + } + + checkContextMenu(menuItems); + } + + let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + + if (options.postCheckContextMenuFn) { + yield options.postCheckContextMenuFn(); + info("Completed postCheckContextMenuFn"); + } + + contextMenu.hidePopup(); + yield awaitPopupHidden; +} diff --git a/browser/base/content/test/general/ctxmenu-image.png b/browser/base/content/test/general/ctxmenu-image.png Binary files differnew file mode 100644 index 000000000..4c3be5084 --- /dev/null +++ b/browser/base/content/test/general/ctxmenu-image.png diff --git a/browser/base/content/test/general/discovery.html b/browser/base/content/test/general/discovery.html new file mode 100644 index 000000000..1679e6545 --- /dev/null +++ b/browser/base/content/test/general/discovery.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<html> + <head id="linkparent"> + <title>Autodiscovery Test</title> + </head> + <body> + </body> +</html> diff --git a/browser/base/content/test/general/download_page.html b/browser/base/content/test/general/download_page.html new file mode 100644 index 000000000..4f9154033 --- /dev/null +++ b/browser/base/content/test/general/download_page.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=676619 +--> + <head> + <title>Test for the download attribute</title> + + </head> + <body> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=676619">Bug 676619</a> + <br/> + <ul> + <li><a href="data:text/plain,Hey What are you looking for?" + download="test.txt" id="link1">Download "test.txt"</a></li> + <li><a href="video.ogg" + download id="link2">Download "video.ogg"</a></li> + <li><a href="video.ogg" + download="just some video" id="link3">Download "just some video"</a></li> + <li><a href="data:text/plain,test" + download="with-target.txt" id="link4">Download "with-target.txt"</a></li> + <li><a href="javascript:(1+2)+''" + download="javascript.txt" id="link5">Download "javascript.txt"</a></li> + </ul> + <script> + var li = document.createElement('li'); + var a = document.createElement('a'); + + a.href = window.URL.createObjectURL(new Blob(["just text"])) ; + a.download = "test.blob"; + a.id = "link6"; + a.textContent = 'Download "test.blob"'; + + li.appendChild(a); + document.getElementsByTagName('ul')[0].appendChild(li); + + window.addEventListener("beforeunload", function (evt) { + document.getElementById("unload-flag").textContent = "Fail"; + }); + </script> + <ul> + <li><a href="http://example.com/" + download="example.com" id="link7" target="_blank">Download "example.com"</a></li> + <ul> + <div id="unload-flag">Okay</div> + </body> +</html> diff --git a/browser/base/content/test/general/dummy_page.html b/browser/base/content/test/general/dummy_page.html new file mode 100644 index 000000000..1a87e2840 --- /dev/null +++ b/browser/base/content/test/general/dummy_page.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Dummy test page</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>Dummy test page</p> +</body> +</html> diff --git a/browser/base/content/test/general/feed_discovery.html b/browser/base/content/test/general/feed_discovery.html new file mode 100644 index 000000000..baecba19b --- /dev/null +++ b/browser/base/content/test/general/feed_discovery.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=377611 +--> + <head> + <title>Test for feed discovery</title> + <meta charset="utf-8"> + + <!-- Straight up standard --> + <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" /> + <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" /> + <link rel="feed" title="3" href="/3.xml" /> + + <!-- rel is a space-separated list --> + <link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" /> + <link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" /> + <link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" /> + <link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" /> + <link rel="meat feed cake" title="8" href="/8.atom" /> + + <!-- rel is case-insensitive --> + <link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" /> + <link rel="fEEd" title="10" href="/10.atom" /> + + <!-- type can have leading and trailing whitespace --> + <link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" /> + + <!-- type is case-insensitive --> + <link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" /> + + <!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't --> + <link rel="feed stylesheet" title="13" href="/13.atom" /> + + <!-- hyphens or letters around rel not allowed --> + <link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" /> + <link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" /> + <link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" /> + + <!-- don't tolerate text/xml if title includes 'rss' not as a word --> + <link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" /> + + <!-- don't tolerate application/xml if title includes 'rss' not as a word --> + <link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" /> + + <!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word --> + <link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" /> + + <!-- don't tolerate random types --> + <link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" /> + + <!-- don't find Atom by title --> + <link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" /> + + <!-- don't find application/rss+xml by title --> + <link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" /> + + <!-- don't find application/rdf+xml by title --> + <link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" /> + + <!-- don't find application/xml by title --> + <link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" /> + + <!-- don't find text/xml by title --> + <link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" /> + + <!-- alternate and stylesheet isn't a feed --> + <link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" /> + </head> + <body> + </body> +</html> + diff --git a/browser/base/content/test/general/feed_tab.html b/browser/base/content/test/general/feed_tab.html new file mode 100644 index 000000000..50903f48b --- /dev/null +++ b/browser/base/content/test/general/feed_tab.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=458579 +--> + <head> + <title>Test for page info feeds tab</title> + + <!-- Straight up standard --> + <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" /> + <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" /> + <link rel="feed" title="3" href="/3.xml" /> + + </head> + <body> + </body> +</html> diff --git a/browser/base/content/test/general/file_bug1045809_1.html b/browser/base/content/test/general/file_bug1045809_1.html new file mode 100644 index 000000000..9baf2d45d --- /dev/null +++ b/browser/base/content/test/general/file_bug1045809_1.html @@ -0,0 +1,7 @@ +<html> + <head> + </head> + <body> + <iframe src="http://test1.example.com/browser/browser/base/content/test/general/file_bug1045809_2.html"></iframe> + </body> +</html> diff --git a/browser/base/content/test/general/file_bug1045809_2.html b/browser/base/content/test/general/file_bug1045809_2.html new file mode 100644 index 000000000..67a297dbc --- /dev/null +++ b/browser/base/content/test/general/file_bug1045809_2.html @@ -0,0 +1,7 @@ +<html> + <head> + </head> + <body> + <div id="mixedContentContainer">Mixed Content is here</div> + </body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_1.html b/browser/base/content/test/general/file_bug822367_1.html new file mode 100644 index 000000000..62f42d226 --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_1.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 1 for Mixed Content Blocker User Override - Mixed Script +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + </div> + <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_1.js b/browser/base/content/test/general/file_bug822367_1.js new file mode 100644 index 000000000..175de363b --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_1.js @@ -0,0 +1 @@ +document.getElementById('p1').innerHTML="hello"; diff --git a/browser/base/content/test/general/file_bug822367_2.html b/browser/base/content/test/general/file_bug822367_2.html new file mode 100644 index 000000000..fe56ee213 --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_2.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 2 for Mixed Content Blocker User Override - Mixed Display +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 822367 - Mixed Display</title> +</head> +<body> + <div id="testContent"> + <img src="http://example.com/tests/image/test/mochitest/blue.png"> + </div> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_3.html b/browser/base/content/test/general/file_bug822367_3.html new file mode 100644 index 000000000..c1ff2c000 --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_3.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 3 for Mixed Content Blocker User Override - Mixed Script and Display +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 3 for Bug 822367</title> + <script> + function foo() { + var x = document.createElement('p'); + x.setAttribute("id", "p2"); + x.innerHTML = "bye"; + document.getElementById("testContent").appendChild(x); + } + </script> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + <img src="http://example.com/tests/image/test/mochitest/blue.png" onload="foo()"> + </div> + <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_4.html b/browser/base/content/test/general/file_bug822367_4.html new file mode 100644 index 000000000..9a073143f --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_4.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 4 for Mixed Content Blocker User Override - Mixed Script and Display +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 4 for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + </div> + <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_4.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_4.js b/browser/base/content/test/general/file_bug822367_4.js new file mode 100644 index 000000000..301db89c7 --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_4.js @@ -0,0 +1 @@ +document.location = "https://example.com/browser/browser/base/content/test/general/file_bug822367_4B.html"; diff --git a/browser/base/content/test/general/file_bug822367_4B.html b/browser/base/content/test/general/file_bug822367_4B.html new file mode 100644 index 000000000..76ea2b623 --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_4B.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 4B for Mixed Content Blocker User Override - Location Changed +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 4B Location Change for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + </div> + <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_5.html b/browser/base/content/test/general/file_bug822367_5.html new file mode 100644 index 000000000..3c9a9317e --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_5.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 5 for Mixed Content Blocker User Override - Mixed Script in document.open() +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 5 for Bug 822367</title> + <script> + function createDoc() + { + var doc=document.open("text/html", "replace"); + doc.write('<!DOCTYPE html><html><body><p id="p1">This is some content</p><script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">\<\/script\>\<\/body>\<\/html>'); + doc.close(); + } + </script> +</head> +<body> + <div id="testContent"> + <img src="https://example.com/tests/image/test/mochitest/blue.png" onload="createDoc()"> + </div> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug822367_6.html b/browser/base/content/test/general/file_bug822367_6.html new file mode 100644 index 000000000..baa5674c2 --- /dev/null +++ b/browser/base/content/test/general/file_bug822367_6.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 6 for Mixed Content Blocker User Override - Mixed Script in document.open() within an iframe +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 6 for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <iframe name="f1" id="f1" src="https://example.com/browser/browser/base/content/test/general/file_bug822367_5.html"></iframe> + </div> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug902156.js b/browser/base/content/test/general/file_bug902156.js new file mode 100644 index 000000000..f943dd628 --- /dev/null +++ b/browser/base/content/test/general/file_bug902156.js @@ -0,0 +1,5 @@ +/* + * Once the mixed content blocker is disabled for the page, this scripts loads + * and updates the text inside the div container. + */ +document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled"; diff --git a/browser/base/content/test/general/file_bug902156_1.html b/browser/base/content/test/general/file_bug902156_1.html new file mode 100644 index 000000000..e3625de99 --- /dev/null +++ b/browser/base/content/test/general/file_bug902156_1.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 902156 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=902156 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 902156</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug902156_2.html b/browser/base/content/test/general/file_bug902156_2.html new file mode 100644 index 000000000..25aff3349 --- /dev/null +++ b/browser/base/content/test/general/file_bug902156_2.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 2 for Bug 902156 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=902156 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 902156</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <a href="https://test2.example.com/browser/browser/base/content/test/general/file_bug902156_1.html" + id="mctestlink" target="_top">Go to http site</a> + <script src="http://test2.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug902156_3.html b/browser/base/content/test/general/file_bug902156_3.html new file mode 100644 index 000000000..65805adff --- /dev/null +++ b/browser/base/content/test/general/file_bug902156_3.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3 for Bug 902156 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=902156 +--> +<head> + <meta charset="utf-8"> + <title>Test 3 for Bug 902156</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug906190.js b/browser/base/content/test/general/file_bug906190.js new file mode 100644 index 000000000..f943dd628 --- /dev/null +++ b/browser/base/content/test/general/file_bug906190.js @@ -0,0 +1,5 @@ +/* + * Once the mixed content blocker is disabled for the page, this scripts loads + * and updates the text inside the div container. + */ +document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled"; diff --git a/browser/base/content/test/general/file_bug906190.sjs b/browser/base/content/test/general/file_bug906190.sjs new file mode 100644 index 000000000..bff126874 --- /dev/null +++ b/browser/base/content/test/general/file_bug906190.sjs @@ -0,0 +1,17 @@ +function handleRequest(request, response) { + var page = "<!DOCTYPE html><html><body>bug 906190</body></html>"; + var path = "https://test1.example.com/browser/browser/base/content/test/general/"; + var url; + + if (request.queryString.includes('bad-redirection=1')) { + url = path + "this_page_does_not_exist.html"; + } else { + url = path + "file_bug906190_redirected.html"; + } + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "302", "Found"); + response.setHeader("Location", url, false); + response.write(page); +} diff --git a/browser/base/content/test/general/file_bug906190_1.html b/browser/base/content/test/general/file_bug906190_1.html new file mode 100644 index 000000000..cbb3cac26 --- /dev/null +++ b/browser/base/content/test/general/file_bug906190_1.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 906190</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug906190_2.html b/browser/base/content/test/general/file_bug906190_2.html new file mode 100644 index 000000000..70c7c61cf --- /dev/null +++ b/browser/base/content/test/general/file_bug906190_2.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 2 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 906190</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test2.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug906190_3_4.html b/browser/base/content/test/general/file_bug906190_3_4.html new file mode 100644 index 000000000..aea6648a9 --- /dev/null +++ b/browser/base/content/test/general/file_bug906190_3_4.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3 and 4 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <meta http-equiv="refresh" content="0; url=https://test1.example.com/browser/browser/base/content/test/general/file_bug906190_redirected.html"> + <title>Test 3 and 4 for Bug 906190</title> +</head> +<body> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug906190_redirected.html b/browser/base/content/test/general/file_bug906190_redirected.html new file mode 100644 index 000000000..cc324bd25 --- /dev/null +++ b/browser/base/content/test/general/file_bug906190_redirected.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Redirected Page of Test 3 to 6 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <title>Redirected Page for Bug 906190</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug970276_favicon1.ico b/browser/base/content/test/general/file_bug970276_favicon1.ico Binary files differnew file mode 100644 index 000000000..d44438903 --- /dev/null +++ b/browser/base/content/test/general/file_bug970276_favicon1.ico diff --git a/browser/base/content/test/general/file_bug970276_favicon2.ico b/browser/base/content/test/general/file_bug970276_favicon2.ico Binary files differnew file mode 100644 index 000000000..d44438903 --- /dev/null +++ b/browser/base/content/test/general/file_bug970276_favicon2.ico diff --git a/browser/base/content/test/general/file_bug970276_popup1.html b/browser/base/content/test/general/file_bug970276_popup1.html new file mode 100644 index 000000000..5ce7dab87 --- /dev/null +++ b/browser/base/content/test/general/file_bug970276_popup1.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test file for bug 970276.</title> + + <!--Set a favicon; that's the whole point of this file.--> + <link rel="icon" href="file_bug970276_favicon1.ico"> +</head> +<body> + Test file for bug 970276. + + <iframe src="file_bug970276_popup2.html"> +</body> +</html> diff --git a/browser/base/content/test/general/file_bug970276_popup2.html b/browser/base/content/test/general/file_bug970276_popup2.html new file mode 100644 index 000000000..0b9e5294e --- /dev/null +++ b/browser/base/content/test/general/file_bug970276_popup2.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test file for bug 970276.</title> + + <!--Set a favicon; that's the whole point of this file.--> + <link rel="icon" href="file_bug970276_favicon2.ico"> +</head> +<body> + Test inner file for bug 970276. +</body> +</html> diff --git a/browser/base/content/test/general/file_csp_block_all_mixedcontent.html b/browser/base/content/test/general/file_csp_block_all_mixedcontent.html new file mode 100644 index 000000000..93a7f13d9 --- /dev/null +++ b/browser/base/content/test/general/file_csp_block_all_mixedcontent.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html><head><meta charset="utf-8"> +<title>Bug 1122236 - CSP: Implement block-all-mixed-content</title> +</head> +<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content"> +<body> +<script src="http://example.com/browser/browser/base/content/test/general/file_csp_block_all_mixedcontent.js"/> +</body> +</html> diff --git a/browser/base/content/test/general/file_csp_block_all_mixedcontent.js b/browser/base/content/test/general/file_csp_block_all_mixedcontent.js new file mode 100644 index 000000000..dc6d6a64e --- /dev/null +++ b/browser/base/content/test/general/file_csp_block_all_mixedcontent.js @@ -0,0 +1,3 @@ +// empty script file just used for testing Bug 1122236. +// Making sure the UI is not degraded when blocking +// mixed content using the CSP directive: block-all-mixed-content. diff --git a/browser/base/content/test/general/file_documentnavigation_frameset.html b/browser/base/content/test/general/file_documentnavigation_frameset.html new file mode 100644 index 000000000..beb01addf --- /dev/null +++ b/browser/base/content/test/general/file_documentnavigation_frameset.html @@ -0,0 +1,12 @@ +<html id="outer"> + +<frameset rows="30%, 70%"> + <frame src="data:text/html,<html id='htmlframe1' ><body id='framebody1'><input id='i1'><body></html>"> + <frameset cols="30%, 33%, 34%"> + <frame src="data:text/html,<html id='htmlframe2'><body id='framebody2'><input id='i2'><body></html>"> + <frame src="data:text/html,<html id='htmlframe3'><body id='framebody3'><input id='i3'><body></html>"> + <frame src="data:text/html,<html id='htmlframe4'><body id='framebody4'><input id='i4'><body></html>"> + </frameset> +</frameset> + +</html> diff --git a/browser/base/content/test/general/file_double_close_tab.html b/browser/base/content/test/general/file_double_close_tab.html new file mode 100644 index 000000000..0bead5efc --- /dev/null +++ b/browser/base/content/test/general/file_double_close_tab.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Test page that blocks beforeunload. Used in tests for bug 1050638 and bug 305085</title> + </head> + <body> + This page will block beforeunload. It should still be user-closable at all times. + <script> + window.onbeforeunload = function() { + return "stop"; + }; + </script> + </body> +</html> diff --git a/browser/base/content/test/general/file_favicon_change.html b/browser/base/content/test/general/file_favicon_change.html new file mode 100644 index 000000000..18ac6526b --- /dev/null +++ b/browser/base/content/test/general/file_favicon_change.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i"> +</head> +<body> + <script> + window.addEventListener("PleaseChangeFavicon", function() { + var ico = document.getElementById("i"); + ico.setAttribute("href", "http://example.org/other-icon"); + }); + </script> +</body></html> diff --git a/browser/base/content/test/general/file_favicon_change_not_in_document.html b/browser/base/content/test/general/file_favicon_change_not_in_document.html new file mode 100644 index 000000000..deebb07dc --- /dev/null +++ b/browser/base/content/test/general/file_favicon_change_not_in_document.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i"> +</head> +<body onload="onload()"> + <script> + function onload() { + var ico = document.createElement("link"); + ico.setAttribute("rel", "icon"); + ico.setAttribute("type", "image/ico"); + ico.setAttribute("href", "http://example.org/other-icon"); + setTimeout(function() { + ico.setAttribute("href", "http://example.org/yet-another-icon"); + document.getElementById("i").remove(); + document.head.appendChild(ico); + }, 1000); + } + </script> +</body></html> + diff --git a/browser/base/content/test/general/file_fullscreen-window-open.html b/browser/base/content/test/general/file_fullscreen-window-open.html new file mode 100644 index 000000000..1584f4c98 --- /dev/null +++ b/browser/base/content/test/general/file_fullscreen-window-open.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test for window.open() when browser is in fullscreen</title> + </head> + <body> + <script> + window.addEventListener("load", function onLoad() { + window.removeEventListener("load", onLoad, true); + + document.getElementById("test").addEventListener("click", onClick, true); + }, true); + + function onClick(aEvent) { + aEvent.preventDefault(); + + var dataStr = aEvent.target.getAttribute("data-test-param"); + var data = JSON.parse(dataStr); + window.open(data.uri, data.title, data.option); + } + </script> + <a id="test" href="" data-test-param="">Test</a> + </body> +</html> diff --git a/browser/base/content/test/general/file_generic_favicon.ico b/browser/base/content/test/general/file_generic_favicon.ico Binary files differnew file mode 100644 index 000000000..d44438903 --- /dev/null +++ b/browser/base/content/test/general/file_generic_favicon.ico diff --git a/browser/base/content/test/general/file_mediaPlayback.html b/browser/base/content/test/general/file_mediaPlayback.html new file mode 100644 index 000000000..a6979287e --- /dev/null +++ b/browser/base/content/test/general/file_mediaPlayback.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<audio src="audio.ogg" controls loop> diff --git a/browser/base/content/test/general/file_mixedContentFramesOnHttp.html b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html new file mode 100644 index 000000000..3bd16aea5 --- /dev/null +++ b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1182551</title> +</head> +<body> + <p>Test for Bug 1182551. This is an HTTP top level page. We include an HTTPS iframe that loads mixed passive content.</p> + <iframe src="https://example.org/browser/browser/base/content/test/general/file_mixedPassiveContent.html"></iframe> +</body> +</html> diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload.html b/browser/base/content/test/general/file_mixedContentFromOnunload.html new file mode 100644 index 000000000..fb28a2889 --- /dev/null +++ b/browser/base/content/test/general/file_mixedContentFromOnunload.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test for https://bugzilla.mozilla.org/show_bug.cgi?id=947079 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 947079</title> +</head> +<body> + <p>Test for Bug 947079</p> + <script> + window.addEventListener('unload', function() { + new Image().src = 'http://mochi.test:8888/tests/image/test/mochitest/blue.png'; + }); + </script> +</body> +</html> diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html b/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html new file mode 100644 index 000000000..1d027b036 --- /dev/null +++ b/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 1 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079 +Page with no insecure subresources +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 947079</title> +</head> +<body> + <p>There are no insecure resource loads on this page</p> +</body> +</html> diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html b/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html new file mode 100644 index 000000000..4813337cc --- /dev/null +++ b/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 2 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079 +Page with an insecure image load +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 947079</title> +</head> +<body> + <p>Page with http image load</p> + <img src="http://test2.example.com/tests/image/test/mochitest/blue.png"> +</body> +</html> diff --git a/browser/base/content/test/general/file_mixedPassiveContent.html b/browser/base/content/test/general/file_mixedPassiveContent.html new file mode 100644 index 000000000..a60ac94e8 --- /dev/null +++ b/browser/base/content/test/general/file_mixedPassiveContent.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551 +--> +<head> + <meta charset="utf-8"> + <title>HTTPS page with HTTP image</title> +</head> +<body> + <img src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"> +</body> +</html> diff --git a/browser/base/content/test/general/file_trackingUI_6.html b/browser/base/content/test/general/file_trackingUI_6.html new file mode 100644 index 000000000..52e1ae63f --- /dev/null +++ b/browser/base/content/test/general/file_trackingUI_6.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Testing the shield from fetch and XHR</title> +</head> +<body> + <p>Hello there!</p> + <script type="application/javascript; version=1.8"> + function test_fetch() { + let url = "http://trackertest.org/browser/browser/base/content/test/general/file_trackingUI_6.js"; + return fetch(url); + } + </script> +</body> +</html> diff --git a/browser/base/content/test/general/file_trackingUI_6.js b/browser/base/content/test/general/file_trackingUI_6.js new file mode 100644 index 000000000..f7ac687cf --- /dev/null +++ b/browser/base/content/test/general/file_trackingUI_6.js @@ -0,0 +1,2 @@ +/* Some code goes here! */ +void 0; diff --git a/browser/base/content/test/general/file_trackingUI_6.js^headers^ b/browser/base/content/test/general/file_trackingUI_6.js^headers^ new file mode 100644 index 000000000..cb762eff8 --- /dev/null +++ b/browser/base/content/test/general/file_trackingUI_6.js^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/browser/base/content/test/general/file_with_favicon.html b/browser/base/content/test/general/file_with_favicon.html new file mode 100644 index 000000000..0702b4aab --- /dev/null +++ b/browser/base/content/test/general/file_with_favicon.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test file for bugs with favicons</title> + + <!--Set a favicon; that's the whole point of this file.--> + <link rel="icon" href="file_generic_favicon.ico"> +</head> +<body> + Test file for bugs with favicons +</body> +</html> diff --git a/browser/base/content/test/general/fxa_profile_handler.sjs b/browser/base/content/test/general/fxa_profile_handler.sjs new file mode 100644 index 000000000..7160b76d0 --- /dev/null +++ b/browser/base/content/test/general/fxa_profile_handler.sjs @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is basically an echo server! +// We just grab responseStatus and responseBody query params! + +function reallyHandleRequest(request, response) { + var query = "?" + request.queryString; + + var responseStatus = 200; + var match = /responseStatus=([^&]*)/.exec(query); + if (match) { + responseStatus = parseInt(match[1]); + } + + var responseBody = ""; + match = /responseBody=([^&]*)/.exec(query); + if (match) { + responseBody = decodeURIComponent(match[1]); + } + + response.setStatusLine("1.0", responseStatus, "OK"); + response.write(responseBody); +} + +function handleRequest(request, response) +{ + try { + reallyHandleRequest(request, response); + } catch (e) { + response.setStatusLine("1.0", 500, "NotOK"); + response.write("Error handling request: " + e); + } +} diff --git a/browser/base/content/test/general/gZipOfflineChild.cacheManifest b/browser/base/content/test/general/gZipOfflineChild.cacheManifest new file mode 100644 index 000000000..ae0545d12 --- /dev/null +++ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest @@ -0,0 +1,2 @@ +CACHE MANIFEST +gZipOfflineChild.html diff --git a/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^ new file mode 100644 index 000000000..257f2eb60 --- /dev/null +++ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^ @@ -0,0 +1 @@ +Content-Type: text/cache-manifest diff --git a/browser/base/content/test/general/gZipOfflineChild.html b/browser/base/content/test/general/gZipOfflineChild.html Binary files differnew file mode 100644 index 000000000..ea2caa125 --- /dev/null +++ b/browser/base/content/test/general/gZipOfflineChild.html diff --git a/browser/base/content/test/general/gZipOfflineChild.html^headers^ b/browser/base/content/test/general/gZipOfflineChild.html^headers^ new file mode 100644 index 000000000..4204d8601 --- /dev/null +++ b/browser/base/content/test/general/gZipOfflineChild.html^headers^ @@ -0,0 +1,2 @@ +Content-Type: text/html +Content-Encoding: gzip diff --git a/browser/base/content/test/general/gZipOfflineChild_uncompressed.html b/browser/base/content/test/general/gZipOfflineChild_uncompressed.html new file mode 100644 index 000000000..4ab8f8d5e --- /dev/null +++ b/browser/base/content/test/general/gZipOfflineChild_uncompressed.html @@ -0,0 +1,21 @@ +<html manifest="gZipOfflineChild.cacheManifest"> +<head> + <!-- This file is gzipped to create gZipOfflineChild.html --> +<title></title> +<script type="text/javascript"> + +function finish(success) { + window.parent.postMessage(success, "*"); +} + +applicationCache.oncached = function() { finish("oncache"); } +applicationCache.onnoupdate = function() { finish("onupdate"); } +applicationCache.onerror = function() { finish("onerror"); } + +</script> +</head> + +<body> +<h1>Child</h1> +</body> +</html> diff --git a/browser/base/content/test/general/head.js b/browser/base/content/test/general/head.js new file mode 100644 index 000000000..6c28615fe --- /dev/null +++ b/browser/base/content/test/general/head.js @@ -0,0 +1,1069 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler", + "resource:///modules/ContentCrashHandlers.jsm"); + +/** + * Wait for a <notification> to be closed then call the specified callback. + */ +function waitForNotificationClose(notification, cb) { + let parent = notification.parentNode; + + let observer = new MutationObserver(function onMutatations(mutations) { + for (let mutation of mutations) { + for (let i = 0; i < mutation.removedNodes.length; i++) { + let node = mutation.removedNodes.item(i); + if (node != notification) { + continue; + } + observer.disconnect(); + cb(); + } + } + }); + observer.observe(parent, {childList: true}); +} + +function closeAllNotifications () { + let notificationBox = document.getElementById("global-notificationbox"); + + if (!notificationBox || !notificationBox.currentNotification) { + return Promise.resolve(); + } + + let deferred = Promise.defer(); + for (let notification of notificationBox.allNotifications) { + waitForNotificationClose(notification, function () { + if (notificationBox.allNotifications.length === 0) { + deferred.resolve(); + } + }); + notification.close(); + } + + return deferred.promise; +} + +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + executeSoon(aCallback); + } + }, "browser-delayed-startup-finished", false); +} + +function updateTabContextMenu(tab, onOpened) { + let menu = document.getElementById("tabContextMenu"); + if (!tab) + tab = gBrowser.selectedTab; + var evt = new Event(""); + tab.dispatchEvent(evt); + menu.openPopup(tab, "end_after", 0, 0, true, false, evt); + is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab"); + const onFinished = () => menu.hidePopup(); + if (onOpened) { + return Task.spawn(function*() { + yield onOpened(); + onFinished(); + }); + } + onFinished(); + return Promise.resolve(); +} + +function openToolbarCustomizationUI(aCallback, aBrowserWin) { + if (!aBrowserWin) + aBrowserWin = window; + + aBrowserWin.gCustomizeMode.enter(); + + aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() { + aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded); + executeSoon(function() { + aCallback(aBrowserWin) + }); + }); +} + +function closeToolbarCustomizationUI(aCallback, aBrowserWin) { + aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() { + aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded); + executeSoon(aCallback); + }); + + aBrowserWin.gCustomizeMode.exit(); +} + +function waitForCondition(condition, nextTest, errorMsg, retryTimes) { + retryTimes = typeof retryTimes !== 'undefined' ? retryTimes : 30; + var tries = 0; + var interval = setInterval(function() { + if (tries >= retryTimes) { + ok(false, errorMsg); + moveOn(); + } + var conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function() { clearInterval(interval); nextTest(); }; +} + +function promiseWaitForCondition(aConditionFn) { + let deferred = Promise.defer(); + waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass."); + return deferred.promise; +} + +function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) { + return new Promise((resolve) => { + function listener(event) { + info("Saw " + eventName); + object.removeEventListener(eventName, listener, capturing, chrome); + resolve(event); + } + + info("Waiting for " + eventName); + object.addEventListener(eventName, listener, capturing, chrome); + }); +} + +/** + * 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); + }); +} + +function getTestPlugin(aName) { + var pluginName = aName || "Test Plug-in"; + var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + var tags = ph.getPluginTags(); + + // Find the test plugin + for (var i = 0; i < tags.length; i++) { + if (tags[i].name == pluginName) + return tags[i]; + } + ok(false, "Unable to find plugin"); + return null; +} + +// call this to set the test plugin(s) initially expected enabled state. +// it will automatically be reset to it's previous value after the test +// ends +function setTestPluginEnabledState(newEnabledState, pluginName) { + var plugin = getTestPlugin(pluginName); + var oldEnabledState = plugin.enabledState; + plugin.enabledState = newEnabledState; + SimpleTest.registerCleanupFunction(function() { + getTestPlugin(pluginName).enabledState = oldEnabledState; + }); +} + +function pushPrefs(...aPrefs) { + let deferred = Promise.defer(); + SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve); + return deferred.promise; +} + +function updateBlocklist(aCallback) { + var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] + .getService(Ci.nsITimerCallback); + var 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); +} + +function resetBlocklist() { + Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); +} + +function whenNewWindowLoaded(aOptions, aCallback) { + let win = OpenBrowserWindow(aOptions); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + aCallback(win); + }, false); +} + +function promiseWindowWillBeClosed(win) { + return new Promise((resolve, reject) => { + Services.obs.addObserver(function observe(subject, topic) { + if (subject == win) { + Services.obs.removeObserver(observe, topic); + resolve(); + } + }, "domwindowclosed", false); + }); +} + +function promiseWindowClosed(win) { + let promise = promiseWindowWillBeClosed(win); + win.close(); + return promise; +} + +function promiseOpenAndLoadWindow(aOptions, aWaitForDelayedStartup=false) { + let deferred = Promise.defer(); + let win = OpenBrowserWindow(aOptions); + if (aWaitForDelayedStartup) { + Services.obs.addObserver(function onDS(aSubject, aTopic, aData) { + if (aSubject != win) { + return; + } + Services.obs.removeObserver(onDS, "browser-delayed-startup-finished"); + deferred.resolve(win); + }, "browser-delayed-startup-finished", false); + + } else { + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + deferred.resolve(win); + }); + } + return deferred.promise; +} + +/** + * Waits for all pending async statements on the default connection, before + * proceeding with aCallback. + * + * @param aCallback + * Function to be called when done. + * @param aScope + * Scope for the callback. + * @param aArguments + * Arguments array for the callback. + * + * @note The result is achieved by asynchronously executing a query requiring + * a write lock. Since all statements on the same connection are + * serialized, the end of this write operation means that all writes are + * complete. Note that WAL makes so that writers don't block readers, but + * this is a problem only across different connections. + */ +function waitForAsyncUpdates(aCallback, aScope, aArguments) { + let scope = aScope || this; + let args = aArguments || []; + let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) + .DBConnection; + let begin = db.createAsyncStatement("BEGIN EXCLUSIVE"); + begin.executeAsync(); + begin.finalize(); + + let commit = db.createAsyncStatement("COMMIT"); + commit.executeAsync({ + handleResult: function() {}, + handleError: function() {}, + handleCompletion: function(aReason) { + aCallback.apply(scope, args); + } + }); + commit.finalize(); +} + +/** + * Asynchronously check a url is visited. + + * @param aURI The URI. + * @param aExpectedValue The expected value. + * @return {Promise} + * @resolves When the check has been added successfully. + * @rejects JavaScript exception. + */ +function promiseIsURIVisited(aURI, aExpectedValue) { + let deferred = Promise.defer(); + PlacesUtils.asyncHistory.isURIVisited(aURI, function(unused, aIsVisited) { + deferred.resolve(aIsVisited); + }); + + return deferred.promise; +} + +function whenNewTabLoaded(aWindow, aCallback) { + aWindow.BrowserOpenTab(); + + let browser = aWindow.gBrowser.selectedBrowser; + if (browser.contentDocument.readyState === "complete") { + aCallback(); + return; + } + + whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback); +} + +function whenTabLoaded(aTab, aCallback) { + promiseTabLoadEvent(aTab).then(aCallback); +} + +function promiseTabLoaded(aTab) { + let deferred = Promise.defer(); + whenTabLoaded(aTab, deferred.resolve); + return deferred.promise; +} + +/** + * Ensures that the specified URIs are either cleared or not. + * + * @param aURIs + * Array of page URIs + * @param aShouldBeCleared + * True if each visit to the URI should be cleared, false otherwise + */ +function promiseHistoryClearedState(aURIs, aShouldBeCleared) { + let deferred = Promise.defer(); + let callbackCount = 0; + let niceStr = aShouldBeCleared ? "no longer" : "still"; + function callbackDone() { + if (++callbackCount == aURIs.length) + deferred.resolve(); + } + aURIs.forEach(function (aURI) { + PlacesUtils.asyncHistory.isURIVisited(aURI, function(uri, isVisited) { + is(isVisited, !aShouldBeCleared, + "history visit " + uri.spec + " should " + niceStr + " exist"); + callbackDone(); + }); + }); + + return deferred.promise; +} + +/** + * Waits for the next top-level document load in the current browser. The URI + * of the document is compared against aExpectedURL. The load is then stopped + * before it actually starts. + * + * @param aExpectedURL + * The URL of the document that is expected to load. + * @param aStopFromProgressListener + * Whether to cancel the load directly from the progress listener. Defaults to true. + * If you're using this method to avoid hitting the network, you want the default (true). + * However, the browser UI will behave differently for loads stopped directly from + * the progress listener (effectively in the middle of a call to loadURI) and so there + * are cases where you may want to avoid stopping the load directly from within the + * progress listener callback. + * @return promise + */ +function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) { + function content_script(contentStopFromProgressListener) { + let { interfaces: Ci, utils: Cu } = Components; + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + let wp = docShell.QueryInterface(Ci.nsIWebProgress); + + function stopContent(now, uri) { + if (now) { + /* Hammer time. */ + content.stop(); + + /* Let the parent know we're done. */ + sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri }); + } else { + setTimeout(stopContent.bind(null, true, uri), 0); + } + } + + let progressListener = { + onStateChange: function (webProgress, req, flags, status) { + dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n"); + + if (webProgress.isTopLevel && + flags & Ci.nsIWebProgressListener.STATE_START) { + wp.removeProgressListener(progressListener); + + let chan = req.QueryInterface(Ci.nsIChannel); + dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`); + + stopContent(contentStopFromProgressListener, chan.originalURI.spec); + } + }, + QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"]) + }; + wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW); + + /** + * As |this| is undefined and we can't extend |docShell|, adding an unload + * event handler is the easiest way to ensure the weakly referenced + * progress listener is kept alive as long as necessary. + */ + addEventListener("unload", function () { + try { + wp.removeProgressListener(progressListener); + } catch (e) { /* Will most likely fail. */ } + }); + } + + return new Promise((resolve, reject) => { + function complete({ data }) { + is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded"); + mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete); + resolve(); + } + + let mm = aBrowser.messageManager; + mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true); + mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete); + info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL); + }); +} + +/** + * Waits for the next load to complete in any browser or the given browser. + * If a <tabbrowser> is given it waits for a load in any of its browsers. + * + * @return promise + */ +function waitForDocLoadComplete(aBrowser=gBrowser) { + return new Promise(resolve => { + let listener = { + onStateChange: function (webProgress, req, flags, status) { + let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK | + Ci.nsIWebProgressListener.STATE_STOP; + info("Saw state " + flags.toString(16) + " and status " + status.toString(16)); + + // When a load needs to be retargetted to a new process it is cancelled + // with NS_BINDING_ABORTED so ignore that case + if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) { + aBrowser.removeProgressListener(this); + waitForDocLoadComplete.listeners.delete(this); + + let chan = req.QueryInterface(Ci.nsIChannel); + info("Browser loaded " + chan.originalURI.spec); + resolve(); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) + }; + aBrowser.addProgressListener(listener); + waitForDocLoadComplete.listeners.add(listener); + info("Waiting for browser load"); + }); +} + +// Keep a set of progress listeners for waitForDocLoadComplete() to make sure +// they're not GC'ed before we saw the page load. +waitForDocLoadComplete.listeners = new Set(); +registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear()); + +var FullZoomHelper = { + + selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) { + if (!tab) + throw new Error("tab must be given."); + if (gBrowser.selectedTab == tab) + return Promise.resolve(); + + return Promise.all([BrowserTestUtils.switchTab(gBrowser, tab), + this.waitForLocationChange()]); + }, + + removeTabAndWaitForLocationChange: function removeTabAndWaitForLocationChange(tab) { + tab = tab || gBrowser.selectedTab; + let selected = gBrowser.selectedTab == tab; + gBrowser.removeTab(tab); + if (selected) + return this.waitForLocationChange(); + return Promise.resolve(); + }, + + waitForLocationChange: function waitForLocationChange() { + return new Promise(resolve => { + Services.obs.addObserver(function obs(subj, topic, data) { + Services.obs.removeObserver(obs, topic); + resolve(); + }, "browser-fullZoom:location-change", false); + }); + }, + + load: function load(tab, url) { + return new Promise(resolve => { + let didLoad = false; + let didZoom = false; + + promiseTabLoadEvent(tab).then(event => { + didLoad = true; + if (didZoom) + resolve(); + }, true); + + this.waitForLocationChange().then(function () { + didZoom = true; + if (didLoad) + resolve(); + }); + + tab.linkedBrowser.loadURI(url); + }); + }, + + zoomTest: function zoomTest(tab, val, msg) { + is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg); + }, + + enlarge: function enlarge() { + return new Promise(resolve => FullZoom.enlarge(resolve)); + }, + + reduce: function reduce() { + return new Promise(resolve => FullZoom.reduce(resolve)); + }, + + reset: function reset() { + return FullZoom.reset(); + }, + + BACK: 0, + FORWARD: 1, + navigate: function navigate(direction) { + return new Promise(resolve => { + let didPs = false; + let didZoom = false; + + gBrowser.addEventListener("pageshow", function listener(event) { + gBrowser.removeEventListener("pageshow", listener, true); + didPs = true; + if (didZoom) + resolve(); + }, true); + + if (direction == this.BACK) + gBrowser.goBack(); + else if (direction == this.FORWARD) + gBrowser.goForward(); + + this.waitForLocationChange().then(function () { + didZoom = true; + if (didPs) + resolve(); + }); + }); + }, + + failAndContinue: function failAndContinue(func) { + return function (err) { + ok(false, err); + func(); + }; + }, +}; + +/** + * 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; +} + +/** + * Returns a Promise that resolves once a new tab has been opened in + * a xul:tabbrowser. + * + * @param aTabBrowser + * The xul:tabbrowser to monitor for a new tab. + * @return {Promise} + * Resolved when the new tab has been opened. + * @resolves to the TabOpen event that was fired. + * @rejects Never. + */ +function waitForNewTabEvent(aTabBrowser) { + return promiseWaitForEvent(aTabBrowser.tabContainer, "TabOpen"); +} + +/** + * Test the state of the identity box and control center to make + * sure they are correctly showing the expected mixed content states. + * + * @note The checks are done synchronously, but new code should wait on the + * returned Promise object to ensure the identity panel has closed. + * Bug 1221114 is filed to fix the existing code. + * + * @param tabbrowser + * @param Object states + * MUST include the following properties: + * { + * activeLoaded: true|false, + * activeBlocked: true|false, + * passiveLoaded: true|false, + * } + * + * @return {Promise} + * @resolves When the operation has finished and the identity panel has closed. + */ +function assertMixedContentBlockingState(tabbrowser, states = {}) { + if (!tabbrowser || !("activeLoaded" in states) || + !("activeBlocked" in states) || !("passiveLoaded" in states)) { + throw new Error("assertMixedContentBlockingState requires a browser and a states object"); + } + + let {passiveLoaded, activeLoaded, activeBlocked} = states; + let {gIdentityHandler} = tabbrowser.ownerGlobal; + let doc = tabbrowser.ownerDocument; + let identityBox = gIdentityHandler._identityBox; + let classList = identityBox.classList; + let connectionIcon = doc.getElementById("connection-icon"); + let connectionIconImage = tabbrowser.ownerGlobal.getComputedStyle(connectionIcon). + getPropertyValue("list-style-image"); + + let stateSecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_SECURE; + let stateBroken = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN; + let stateInsecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_INSECURE; + let stateActiveBlocked = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT; + let stateActiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT; + let statePassiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT; + + is(activeBlocked, !!stateActiveBlocked, "Expected state for activeBlocked matches UI state"); + is(activeLoaded, !!stateActiveLoaded, "Expected state for activeLoaded matches UI state"); + is(passiveLoaded, !!statePassiveLoaded, "Expected state for passiveLoaded matches UI state"); + + if (stateInsecure) { + // HTTP request, there should be no MCB classes for the identity box and the non secure icon + // should always be visible regardless of MCB state. + ok(classList.contains("unknownIdentity"), "unknownIdentity on HTTP page"); + is_element_hidden(connectionIcon); + + ok(!classList.contains("mixedActiveContent"), "No MCB icon on HTTP page"); + ok(!classList.contains("mixedActiveBlocked"), "No MCB icon on HTTP page"); + ok(!classList.contains("mixedDisplayContent"), "No MCB icon on HTTP page"); + ok(!classList.contains("mixedDisplayContentLoadedActiveBlocked"), "No MCB icon on HTTP page"); + } else { + // Make sure the identity box UI has the correct mixedcontent states and icons + is(classList.contains("mixedActiveContent"), activeLoaded, + "identityBox has expected class for activeLoaded"); + is(classList.contains("mixedActiveBlocked"), activeBlocked && !passiveLoaded, + "identityBox has expected class for activeBlocked && !passiveLoaded"); + is(classList.contains("mixedDisplayContent"), passiveLoaded && !(activeLoaded || activeBlocked), + "identityBox has expected class for passiveLoaded && !(activeLoaded || activeBlocked)"); + is(classList.contains("mixedDisplayContentLoadedActiveBlocked"), passiveLoaded && activeBlocked, + "identityBox has expected class for passiveLoaded && activeBlocked"); + + is_element_visible(connectionIcon); + if (activeLoaded) { + is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg#icon\")", + "Using active loaded icon"); + } + if (activeBlocked && !passiveLoaded) { + is(connectionIconImage, "url(\"chrome://browser/skin/connection-secure.svg\")", + "Using active blocked icon"); + } + if (passiveLoaded && !(activeLoaded || activeBlocked)) { + is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")", + "Using passive loaded icon"); + } + if (passiveLoaded && activeBlocked) { + is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")", + "Using active blocked and passive loaded icon"); + } + } + + // Make sure the identity popup has the correct mixedcontent states + gIdentityHandler._identityBox.click(); + let popupAttr = doc.getElementById("identity-popup").getAttribute("mixedcontent"); + let bodyAttr = doc.getElementById("identity-popup-securityView-body").getAttribute("mixedcontent"); + + is(popupAttr.includes("active-loaded"), activeLoaded, + "identity-popup has expected attr for activeLoaded"); + is(bodyAttr.includes("active-loaded"), activeLoaded, + "securityView-body has expected attr for activeLoaded"); + + is(popupAttr.includes("active-blocked"), activeBlocked, + "identity-popup has expected attr for activeBlocked"); + is(bodyAttr.includes("active-blocked"), activeBlocked, + "securityView-body has expected attr for activeBlocked"); + + is(popupAttr.includes("passive-loaded"), passiveLoaded, + "identity-popup has expected attr for passiveLoaded"); + is(bodyAttr.includes("passive-loaded"), passiveLoaded, + "securityView-body has expected attr for passiveLoaded"); + + // Make sure the correct icon is visible in the Control Center. + // This logic is controlled with CSS, so this helps prevent regressions there. + let securityView = doc.getElementById("identity-popup-securityView"); + let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView). + getPropertyValue("background-image"); + let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView). + getPropertyValue("background-image"); + + if (stateInsecure) { + is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")", + "CC using 'not secure' icon"); + is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")", + "CC using 'not secure' icon"); + } + + if (stateSecure) { + is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")", + "CC using secure icon"); + is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")", + "CC using secure icon"); + } + + if (stateBroken) { + if (activeLoaded) { + is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")", + "CC using active loaded icon"); + is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")", + "CC using active loaded icon"); + } else if (activeBlocked || passiveLoaded) { + is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")", + "CC using degraded icon"); + is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")", + "CC using degraded icon"); + } else { + // There is a case here with weak ciphers, but no bc tests are handling this yet. + is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")", + "CC using degraded icon"); + is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")", + "CC using degraded icon"); + } + } + + if (activeLoaded || activeBlocked || passiveLoaded) { + doc.getElementById("identity-popup-security-expander").click(); + is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"), + element => !is_hidden(element)).length, 1, + "The 'Learn more' link should be visible once."); + } + + gIdentityHandler._identityPopup.hidden = true; + + // Wait for the panel to be closed before continuing. The promisePopupHidden + // function cannot be used because it's unreliable unless promisePopupShown is + // also called before closing the panel. This cannot be done until all callers + // are made asynchronous (bug 1221114). + return new Promise(resolve => executeSoon(resolve)); +} + +function is_hidden(element) { + var style = element.ownerGlobal.getComputedStyle(element); + if (style.display == "none") + return true; + if (style.visibility != "visible") + return true; + if (style.display == "-moz-popup") + return ["hiding", "closed"].indexOf(element.state) != -1; + + // Hiding a parent element will hide all its children + if (element.parentNode != element.ownerDocument) + return is_hidden(element.parentNode); + + return false; +} + +function is_visible(element) { + var style = element.ownerGlobal.getComputedStyle(element); + if (style.display == "none") + return false; + if (style.visibility != "visible") + return false; + if (style.display == "-moz-popup" && element.state != "open") + return false; + + // Hiding a parent element will hide all its children + if (element.parentNode != element.ownerDocument) + return is_visible(element.parentNode); + + return true; +} + +function is_element_visible(element, msg) { + isnot(element, null, "Element should not be null, when checking visibility"); + ok(is_visible(element), msg || "Element should be visible"); +} + +function is_element_hidden(element, msg) { + isnot(element, null, "Element should not be null, when checking visibility"); + ok(is_hidden(element), msg || "Element should be hidden"); +} + +function promisePopupEvent(popup, eventSuffix) { + let endState = {shown: "open", hidden: "closed"}[eventSuffix]; + + if (popup.state == endState) + return Promise.resolve(); + + let eventType = "popup" + eventSuffix; + let deferred = Promise.defer(); + popup.addEventListener(eventType, function onPopupShown(event) { + popup.removeEventListener(eventType, onPopupShown); + deferred.resolve(); + }); + + return deferred.promise; +} + +function promisePopupShown(popup) { + return promisePopupEvent(popup, "shown"); +} + +function promisePopupHidden(popup) { + return promisePopupEvent(popup, "hidden"); +} + +function promiseNotificationShown(notification) { + let win = notification.browser.ownerGlobal; + if (win.PopupNotifications.panel.state == "open") { + return Promise.resolve(); + } + let panelPromise = promisePopupShown(win.PopupNotifications.panel); + notification.reshow(); + return panelPromise; +} + +/** + * Allows waiting for an observer notification once. + * + * @param aTopic + * Notification topic to observe. + * + * @return {Promise} + * @resolves An object with subject and data properties from the observed + * notification. + * @rejects Never. + */ +function promiseTopicObserved(aTopic) +{ + return new Promise((resolve) => { + Services.obs.addObserver( + function PTO_observe(aSubject, aTopic2, aData) { + Services.obs.removeObserver(PTO_observe, aTopic2); + resolve({subject: aSubject, data: aData}); + }, aTopic, false); + }); +} + +function promiseNewSearchEngine(basename) { + return new Promise((resolve, reject) => { + info("Waiting for engine to be added: " + basename); + let url = getRootDirectory(gTestPath) + basename; + Services.search.addEngine(url, null, "", false, { + onSuccess: function (engine) { + info("Search engine added: " + basename); + registerCleanupFunction(() => Services.search.removeEngine(engine)); + resolve(engine); + }, + onError: function (errCode) { + Assert.ok(false, "addEngine failed with error code " + errCode); + reject(); + }, + }); + }); +} + +// Compares the security state of the page with what is expected +function isSecurityState(expectedState) { + let ui = gTestBrowser.securityUI; + if (!ui) { + ok(false, "No security UI to get the security state"); + return; + } + + const wpl = Components.interfaces.nsIWebProgressListener; + + // determine the security state + let isSecure = ui.state & wpl.STATE_IS_SECURE; + let isBroken = ui.state & wpl.STATE_IS_BROKEN; + let isInsecure = ui.state & wpl.STATE_IS_INSECURE; + + let actualState; + if (isSecure && !(isBroken || isInsecure)) { + actualState = "secure"; + } else if (isBroken && !(isSecure || isInsecure)) { + actualState = "broken"; + } else if (isInsecure && !(isSecure || isBroken)) { + actualState = "insecure"; + } else { + actualState = "unknown"; + } + + is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + "."); +} + +/** + * Resolves when a bookmark with the given uri is added. + */ +function promiseOnBookmarkItemAdded(aExpectedURI) { + return new Promise((resolve, reject) => { + let bookmarksObserver = { + onItemAdded: function (aItemId, aFolderId, aIndex, aItemType, aURI) { + info("Added a bookmark to " + aURI.spec); + PlacesUtils.bookmarks.removeObserver(bookmarksObserver); + if (aURI.equals(aExpectedURI)) { + resolve(); + } + else { + reject(new Error("Added an unexpected bookmark")); + } + }, + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onItemRemoved: function () {}, + onItemChanged: function () {}, + onItemVisited: function () {}, + onItemMoved: function () {}, + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver, + ]) + }; + info("Waiting for a bookmark to be added"); + PlacesUtils.bookmarks.addObserver(bookmarksObserver, false); + }); +} + +function promiseErrorPageLoaded(browser) { + return new Promise(resolve => { + browser.addEventListener("DOMContentLoaded", function onLoad() { + browser.removeEventListener("DOMContentLoaded", onLoad, false, true); + resolve(); + }, false, true); + }); +} + +function* loadBadCertPage(url) { + const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul"; + let exceptionDialogResolved = new Promise(function(resolve) { + // When the certificate exception dialog has opened, click the button to add + // an exception. + let certExceptionDialogObserver = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "cert-exception-ui-ready") { + Services.obs.removeObserver(this, "cert-exception-ui-ready"); + let certExceptionDialog = getCertExceptionDialog(EXCEPTION_DIALOG_URI); + ok(certExceptionDialog, "found exception dialog"); + executeSoon(function() { + certExceptionDialog.documentElement.getButton("extra1").click(); + resolve(); + }); + } + } + }; + + Services.obs.addObserver(certExceptionDialogObserver, + "cert-exception-ui-ready", false); + }); + + let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url); + yield loaded; + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + content.document.getElementById("exceptionDialogButton").click(); + }); + yield exceptionDialogResolved; + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); +} + +// Utility function to get a handle on the certificate exception dialog. +// Modified from toolkit/components/passwordmgr/test/prompt_common.js +function getCertExceptionDialog(aLocation) { + let enumerator = Services.wm.getXULWindowEnumerator(null); + + while (enumerator.hasMoreElements()) { + let win = enumerator.getNext(); + let windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell; + + let containedDocShells = windowDocShell.getDocShellEnumerator( + Ci.nsIDocShellTreeItem.typeChrome, + Ci.nsIDocShell.ENUMERATE_FORWARDS); + while (containedDocShells.hasMoreElements()) { + // Get the corresponding document for this docshell + let childDocShell = containedDocShells.getNext(); + let childDoc = childDocShell.QueryInterface(Ci.nsIDocShell) + .contentViewer + .DOMDocument; + + if (childDoc.location.href == aLocation) { + return childDoc; + } + } + } + return undefined; +} + +function setupRemoteClientsFixture(fixture) { + let oldRemoteClientsGetter = + Object.getOwnPropertyDescriptor(gFxAccounts, "remoteClients").get; + + Object.defineProperty(gFxAccounts, "remoteClients", { + get: function() { return fixture; } + }); + return oldRemoteClientsGetter; +} + +function restoreRemoteClients(getter) { + Object.defineProperty(gFxAccounts, "remoteClients", { + get: getter + }); +} + +function* openMenuItemSubmenu(id) { + let menuPopup = document.getElementById(id).menupopup; + let menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown"); + menuPopup.showPopup(); + yield menuPopupPromise; +} diff --git a/browser/base/content/test/general/head_plain.js b/browser/base/content/test/general/head_plain.js new file mode 100644 index 000000000..3796c7d2b --- /dev/null +++ b/browser/base/content/test/general/head_plain.js @@ -0,0 +1,27 @@ + +function getTestPlugin(pluginName) { + var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"] + .getService(SpecialPowers.Ci.nsIPluginHost); + var tags = ph.getPluginTags(); + var name = pluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + + ok(false, "Could not find plugin tag with plugin name '" + name + "'"); + return null; +} + +// call this to set the test plugin(s) initially expected enabled state. +// it will automatically be reset to it's previous value after the test +// ends +function setTestPluginEnabledState(newEnabledState, pluginName) { + var plugin = getTestPlugin(pluginName); + var oldEnabledState = plugin.enabledState; + plugin.enabledState = newEnabledState; + SimpleTest.registerCleanupFunction(function() { + getTestPlugin(pluginName).enabledState = oldEnabledState; + }); +} diff --git a/browser/base/content/test/general/healthreport_pingData.js b/browser/base/content/test/general/healthreport_pingData.js new file mode 100644 index 000000000..1737baba1 --- /dev/null +++ b/browser/base/content/test/general/healthreport_pingData.js @@ -0,0 +1,17 @@ +var TEST_PINGS = [ + { + type: "test-telemetryArchive-1", + payload: { foo: "bar" }, + date: new Date(2010, 1, 1, 10, 0, 0), + }, + { + type: "test-telemetryArchive-2", + payload: { x: { y: "z"} }, + date: new Date(2010, 1, 1, 11, 0, 0), + }, + { + type: "test-telemetryArchive-3", + payload: { moo: "meh" }, + date: new Date(2010, 1, 1, 12, 0, 0), + }, +]; diff --git a/browser/base/content/test/general/healthreport_testRemoteCommands.html b/browser/base/content/test/general/healthreport_testRemoteCommands.html new file mode 100644 index 000000000..7978914f2 --- /dev/null +++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html @@ -0,0 +1,243 @@ +<html> + <head> + <meta charset="utf-8"> +<script type="application/javascript;version=1.7" + src="healthreport_pingData.js"> +</script> +<script type="application/javascript;version=1.7"> + +function init() { + window.addEventListener("message", doTest, false); + doTest(); +} + +function checkSubmissionValue(payload, expectedValue) { + return payload.enabled == expectedValue; +} + +function isArray(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; +} + +function writeDiagnostic(text) { + let node = document.createTextNode(text); + let br = document.createElement("br"); + document.body.appendChild(node); + document.body.appendChild(br); +} + +function validateCurrentTelemetryEnvironment(data) { + // Simple check for now: check that the received object has the expected + // top-level properties. + const expectedKeys = ["profile", "settings", "system", "build", "partner", "addons"]; + return expectedKeys.every(key => (key in data)); +} + +function validateCurrentTelemetryPingData(ping) { + // Simple check for now: check that the received object has the expected + // top-level properties and that the type and reason match. + const expectedKeys = ["environment", "clientId", "payload", "application", + "version", "type", "id"]; + return expectedKeys.every(key => (key in ping)) && + (ping.type == "main") && + ("info" in ping.payload) && + ("reason" in ping.payload.info) && + (ping.payload.info.reason == "gather-subsession-payload"); +} + +function validateTelemetryPingList(list) { + if (!isArray(list)) { + console.log("Telemetry ping list is not an array."); + return false; + } + + // Telemetry may generate other pings (e.g. "deletion" pings), so filter those + // out. + const TEST_TYPES_REGEX = /^test-telemetryArchive/; + list = list.filter(p => TEST_TYPES_REGEX.test(p.type)); + + if (list.length != TEST_PINGS.length) { + console.log("Telemetry ping length is not correct."); + return false; + } + + let valid = true; + for (let i=0; i<list.length; ++i) { + let received = list[i]; + let expected = TEST_PINGS[i]; + if (received.type != expected.type || + received.timestampCreated != expected.date.getTime()) { + writeDiagnostic("Telemetry ping " + i + " does not match."); + writeDiagnostic("Expected: " + JSON.stringify(expected)); + writeDiagnostic("Received: " + JSON.stringify(received)); + valid = false; + } else { + writeDiagnostic("Telemetry ping " + i + " matches."); + } + } + + return true; +} + +function validateTelemetryPingData(expected, received) { + const receivedDate = new Date(received.creationDate); + if (received.id != expected.id || + received.type != expected.type || + receivedDate.getTime() != expected.date.getTime()) { + writeDiagnostic("Telemetry ping data for " + expected.id + " doesn't match."); + writeDiagnostic("Expected: " + JSON.stringify(expected)); + writeDiagnostic("Received: " + JSON.stringify(received)); + return false; + } + + writeDiagnostic("Telemetry ping data for " + expected.id + " matched."); + return true; +} + +var tests = [ +{ + info: "Checking initial value is enabled", + event: "RequestCurrentPrefs", + payloadType: "prefs", + validateResponse: function(payload) { + return checkSubmissionValue(payload, true); + }, +}, +{ + info: "Verifying disabling works", + event: "DisableDataSubmission", + payloadType: "prefs", + validateResponse: function(payload) { + return checkSubmissionValue(payload, false); + }, +}, +{ + info: "Verifying we're still disabled", + event: "RequestCurrentPrefs", + payloadType: "prefs", + validateResponse: function(payload) { + return checkSubmissionValue(payload, false); + }, +}, +{ + info: "Verifying that we can get the current ping data while submission is disabled", + event: "RequestCurrentPingData", + payloadType: "telemetry-current-ping-data", + validateResponse: function(payload) { + return validateCurrentTelemetryPingData(payload); + }, +}, +{ + info: "Verifying enabling works", + event: "EnableDataSubmission", + payloadType: "prefs", + validateResponse: function(payload) { + return checkSubmissionValue(payload, true); + }, +}, +{ + info: "Verifying we're still re-enabled", + event: "RequestCurrentPrefs", + payloadType: "prefs", + validateResponse: function(payload) { + return checkSubmissionValue(payload, true); + }, +}, +{ + info: "Verifying that we can get the current Telemetry environment data", + event: "RequestCurrentEnvironment", + payloadType: "telemetry-current-environment-data", + validateResponse: function(payload) { + return validateCurrentTelemetryEnvironment(payload); + }, +}, +{ + info: "Verifying that we can get the current Telemetry ping data", + event: "RequestCurrentPingData", + payloadType: "telemetry-current-ping-data", + validateResponse: function(payload) { + return validateCurrentTelemetryPingData(payload); + }, +}, +{ + info: "Verifying that we get the proper Telemetry ping list", + event: "RequestTelemetryPingList", + payloadType: "telemetry-ping-list", + validateResponse: function(payload) { + // Validate the ping list + if (!validateTelemetryPingList(payload)) { + return false; + } + + // Now that we received the ping ids, set up additional test tasks + // that check loading the individual pings. + for (let i=0; i<TEST_PINGS.length; ++i) { + TEST_PINGS[i].id = payload[i].id; + tests.push({ + info: "Verifying that we can get the proper Telemetry ping data #" + (i + 1), + event: "RequestTelemetryPingData", + eventData: { id: TEST_PINGS[i].id }, + payloadType: "telemetry-ping-data", + validateResponse: function(payload) { + return validateTelemetryPingData(TEST_PINGS[i], payload.pingData); + }, + }); + } + + return true; + }, +}, +]; + +var currentTest = -1; +function doTest(evt) { + if (evt) { + if (currentTest < 0 || !evt.data.content) + return; // not yet testing + + var test = tests[currentTest]; + if (evt.data.type != test.payloadType) + return; // skip unrequested events + + var error = JSON.stringify(evt.data.content); + var pass = false; + try { + pass = test.validateResponse(evt.data.content) + } catch (e) {} + reportResult(test.info, pass, error); + } + // start the next test if there are any left + if (tests[++currentTest]) + sendToBrowser(tests[currentTest].event, tests[currentTest].eventData); + else + reportFinished(); +} + +function reportResult(info, pass, error) { + var data = {type: "testResult", info: info, pass: pass, error: error}; + var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true}); + document.dispatchEvent(event); +} + +function reportFinished(cmd) { + var data = {type: "testsComplete", count: tests.length}; + var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true}); + document.dispatchEvent(event); +} + +function sendToBrowser(type, eventData) { + eventData = eventData || {}; + let detail = {command: type}; + for (let key of Object.keys(eventData)) { + detail[key] = eventData[key]; + } + + var event = new CustomEvent("RemoteHealthReportCommand", {detail: detail, bubbles: true}); + document.dispatchEvent(event); +} + +</script> + </head> + <body onload="init()"> + </body> +</html> diff --git a/browser/base/content/test/general/insecure_opener.html b/browser/base/content/test/general/insecure_opener.html new file mode 100644 index 000000000..26ed014f6 --- /dev/null +++ b/browser/base/content/test/general/insecure_opener.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <a id="link" target="_blank" href="https://example.com/browser/toolkit/components/passwordmgr/test/browser/form_basic.html">Click me, I'm "secure".</a> + </body> +</html> diff --git a/browser/base/content/test/general/mochitest.ini b/browser/base/content/test/general/mochitest.ini new file mode 100644 index 000000000..a07a01b87 --- /dev/null +++ b/browser/base/content/test/general/mochitest.ini @@ -0,0 +1,27 @@ +[DEFAULT] +support-files = + audio.ogg + bug364677-data.xml + bug364677-data.xml^headers^ + bug395533-data.txt + contextmenu_common.js + ctxmenu-image.png + head_plain.js + offlineByDefault.js + offlineChild.cacheManifest + offlineChild.cacheManifest^headers^ + offlineChild.html + offlineChild2.cacheManifest + offlineChild2.cacheManifest^headers^ + offlineChild2.html + offlineEvent.cacheManifest + offlineEvent.cacheManifest^headers^ + offlineEvent.html + subtst_contextmenu.html + video.ogg + !/image/test/mochitest/blue.png + +[test_bug364677.html] +[test_bug395533.html] +[test_offlineNotification.html] +skip-if = e10s # Bug 1257785 diff --git a/browser/base/content/test/general/moz.png b/browser/base/content/test/general/moz.png Binary files differnew file mode 100644 index 000000000..769c63634 --- /dev/null +++ b/browser/base/content/test/general/moz.png diff --git a/browser/base/content/test/general/navigating_window_with_download.html b/browser/base/content/test/general/navigating_window_with_download.html new file mode 100644 index 000000000..6b0918941 --- /dev/null +++ b/browser/base/content/test/general/navigating_window_with_download.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> + <head><title>This window will navigate while you're downloading something</title></head> + <body> + <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe> + </body> +</html> diff --git a/browser/base/content/test/general/offlineByDefault.js b/browser/base/content/test/general/offlineByDefault.js new file mode 100644 index 000000000..72f7e52a0 --- /dev/null +++ b/browser/base/content/test/general/offlineByDefault.js @@ -0,0 +1,17 @@ +var offlineByDefault = { + defaultValue: false, + prefBranch: SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch), + set: function(allow) { + try { + this.defaultValue = this.prefBranch.getBoolPref("offline-apps.allow_by_default"); + } catch (e) { + this.defaultValue = false + } + this.prefBranch.setBoolPref("offline-apps.allow_by_default", allow); + }, + reset: function() { + this.prefBranch.setBoolPref("offline-apps.allow_by_default", this.defaultValue); + } +} + +offlineByDefault.set(false); diff --git a/browser/base/content/test/general/offlineChild.cacheManifest b/browser/base/content/test/general/offlineChild.cacheManifest new file mode 100644 index 000000000..091fe7194 --- /dev/null +++ b/browser/base/content/test/general/offlineChild.cacheManifest @@ -0,0 +1,2 @@ +CACHE MANIFEST +offlineChild.html diff --git a/browser/base/content/test/general/offlineChild.cacheManifest^headers^ b/browser/base/content/test/general/offlineChild.cacheManifest^headers^ new file mode 100644 index 000000000..257f2eb60 --- /dev/null +++ b/browser/base/content/test/general/offlineChild.cacheManifest^headers^ @@ -0,0 +1 @@ +Content-Type: text/cache-manifest diff --git a/browser/base/content/test/general/offlineChild.html b/browser/base/content/test/general/offlineChild.html new file mode 100644 index 000000000..43f225b3b --- /dev/null +++ b/browser/base/content/test/general/offlineChild.html @@ -0,0 +1,20 @@ +<html manifest="offlineChild.cacheManifest"> +<head> +<title></title> +<script type="text/javascript"> + +function finish(success) { + window.parent.postMessage(success ? "success" : "failure", "*"); +} + +applicationCache.oncached = function() { finish(true); } +applicationCache.onnoupdate = function() { finish(true); } +applicationCache.onerror = function() { finish(false); } + +</script> +</head> + +<body> +<h1>Child</h1> +</body> +</html> diff --git a/browser/base/content/test/general/offlineChild2.cacheManifest b/browser/base/content/test/general/offlineChild2.cacheManifest new file mode 100644 index 000000000..19efe54fe --- /dev/null +++ b/browser/base/content/test/general/offlineChild2.cacheManifest @@ -0,0 +1,2 @@ +CACHE MANIFEST +offlineChild2.html diff --git a/browser/base/content/test/general/offlineChild2.cacheManifest^headers^ b/browser/base/content/test/general/offlineChild2.cacheManifest^headers^ new file mode 100644 index 000000000..257f2eb60 --- /dev/null +++ b/browser/base/content/test/general/offlineChild2.cacheManifest^headers^ @@ -0,0 +1 @@ +Content-Type: text/cache-manifest diff --git a/browser/base/content/test/general/offlineChild2.html b/browser/base/content/test/general/offlineChild2.html new file mode 100644 index 000000000..ac762e759 --- /dev/null +++ b/browser/base/content/test/general/offlineChild2.html @@ -0,0 +1,20 @@ +<html manifest="offlineChild2.cacheManifest"> +<head> +<title></title> +<script type="text/javascript"> + +function finish(success) { + window.parent.postMessage(success ? "success" : "failure", "*"); +} + +applicationCache.oncached = function() { finish(true); } +applicationCache.onnoupdate = function() { finish(true); } +applicationCache.onerror = function() { finish(false); } + +</script> +</head> + +<body> +<h1>Child</h1> +</body> +</html> diff --git a/browser/base/content/test/general/offlineEvent.cacheManifest b/browser/base/content/test/general/offlineEvent.cacheManifest new file mode 100644 index 000000000..091fe7194 --- /dev/null +++ b/browser/base/content/test/general/offlineEvent.cacheManifest @@ -0,0 +1,2 @@ +CACHE MANIFEST +offlineChild.html diff --git a/browser/base/content/test/general/offlineEvent.cacheManifest^headers^ b/browser/base/content/test/general/offlineEvent.cacheManifest^headers^ new file mode 100644 index 000000000..257f2eb60 --- /dev/null +++ b/browser/base/content/test/general/offlineEvent.cacheManifest^headers^ @@ -0,0 +1 @@ +Content-Type: text/cache-manifest diff --git a/browser/base/content/test/general/offlineEvent.html b/browser/base/content/test/general/offlineEvent.html new file mode 100644 index 000000000..f6e2494e2 --- /dev/null +++ b/browser/base/content/test/general/offlineEvent.html @@ -0,0 +1,9 @@ +<html manifest="offlineEvent.cacheManifest"> +<head> +<title></title> +</head> + +<body> +<h1>Child</h1> +</body> +</html> diff --git a/browser/base/content/test/general/offlineQuotaNotification.cacheManifest b/browser/base/content/test/general/offlineQuotaNotification.cacheManifest new file mode 100644 index 000000000..2e210abd2 --- /dev/null +++ b/browser/base/content/test/general/offlineQuotaNotification.cacheManifest @@ -0,0 +1,7 @@ +CACHE MANIFEST +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +# store a "large" file so an "over quota warning" will be issued - any file +# larger than 1kb and in '_BROWSER_FILES' should be right... +title_test.svg diff --git a/browser/base/content/test/general/offlineQuotaNotification.html b/browser/base/content/test/general/offlineQuotaNotification.html new file mode 100644 index 000000000..b1b91bf9e --- /dev/null +++ b/browser/base/content/test/general/offlineQuotaNotification.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html manifest="offlineQuotaNotification.cacheManifest"> +<head> + <meta charset="utf-8"> + <title>Test offline app quota notification</title> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +</html> diff --git a/browser/base/content/test/general/page_style_sample.html b/browser/base/content/test/general/page_style_sample.html new file mode 100644 index 000000000..54cbaa9e6 --- /dev/null +++ b/browser/base/content/test/general/page_style_sample.html @@ -0,0 +1,41 @@ +<html> + <head> + <title>Test for page style menu</title> + <!-- data-state values: + 0: should not appear in the page style menu + 0-todo: should not appear in the page style menu, but does + 1: should appear in the page style menu + 2: should appear in the page style menu as the selected stylesheet --> + <link data-state="1" href="404.css" title="1" rel="alternate stylesheet"> + <link data-state="0" title="2" rel="alternate stylesheet"> + <link data-state="0" href="404.css" rel="alternate stylesheet"> + <link data-state="0" href="404.css" title="" rel="alternate stylesheet"> + <link data-state="1" href="404.css" title="3" rel="stylesheet alternate"> + <link data-state="1" href="404.css" title="4" rel=" alternate stylesheet "> + <link data-state="1" href="404.css" title="5" rel="alternate stylesheet"> + <link data-state="2" href="404.css" title="6" rel="stylesheet"> + <link data-state="1" href="404.css" title="7" rel="foo stylesheet"> + <link data-state="0" href="404.css" title="8" rel="alternate"> + <link data-state="1" href="404.css" title="9" rel="alternate STYLEsheet"> + <link data-state="1" href="404.css" title="10" rel="alternate stylesheet" media=""> + <link data-state="1" href="404.css" title="11" rel="alternate stylesheet" media="all"> + <link data-state="1" href="404.css" title="12" rel="alternate stylesheet" media="ALL "> + <link data-state="1" href="404.css" title="13" rel="alternate stylesheet" media="screen"> + <link data-state="1" href="404.css" title="14" rel="alternate stylesheet" media=" Screen"> + <link data-state="0" href="404.css" title="15" rel="alternate stylesheet" media="screen foo"> + <link data-state="0" href="404.css" title="16" rel="alternate stylesheet" media="all screen"> + <link data-state="0" href="404.css" title="17" rel="alternate stylesheet" media="foo bar"> + <link data-state="1" href="404.css" title="18" rel="alternate stylesheet" media="all,screen"> + <link data-state="1" href="404.css" title="19" rel="alternate stylesheet" media="all, screen"> + <link data-state="0" href="404.css" title="20" rel="alternate stylesheet" media="all screen"> + <link data-state="0" href="404.css" title="21" rel="alternate stylesheet" media="foo"> + <link data-state="0" href="404.css" title="22" rel="alternate stylesheet" media="allscreen"> + <link data-state="0" href="404.css" title="23" rel="alternate stylesheet" media="_all"> + <link data-state="0" href="404.css" title="24" rel="alternate stylesheet" media="not screen"> + <link data-state="1" href="404.css" title="25" rel="alternate stylesheet" media="only screen"> + <link data-state="1" href="404.css" title="26" rel="alternate stylesheet" media="screen and (min-device-width: 1px)"> + <link data-state="0" href="404.css" title="27" rel="alternate stylesheet" media="screen and (max-device-width: 1px)"> + <style data-state="1" title="28">/* some more styles */</style> + </head> + <body></body> +</html> diff --git a/browser/base/content/test/general/parsingTestHelpers.jsm b/browser/base/content/test/general/parsingTestHelpers.jsm new file mode 100644 index 000000000..69c764483 --- /dev/null +++ b/browser/base/content/test/general/parsingTestHelpers.jsm @@ -0,0 +1,131 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["generateURIsFromDirTree"]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +/* Shorthand constructors to construct an nsI(Local)File and zip reader: */ +const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", Ci.nsIFile, "initWithPath"); +const ZipReader = new Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open"); + + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + + +/** + * Returns a promise that is resolved with a list of files that have one of the + * extensions passed, represented by their nsIURI objects, which exist inside + * the directory passed. + * + * @param dir the directory which to scan for files (nsIFile) + * @param extensions the extensions of files we're interested in (Array). + */ +function generateURIsFromDirTree(dir, extensions) { + if (!Array.isArray(extensions)) { + extensions = [extensions]; + } + let dirQueue = [dir.path]; + return Task.spawn(function*() { + let rv = []; + while (dirQueue.length) { + let nextDir = dirQueue.shift(); + let {subdirs, files} = yield iterateOverPath(nextDir, extensions); + dirQueue.push(...subdirs); + rv.push(...files); + } + return rv; + }); +} + +/** + * Uses OS.File.DirectoryIterator to asynchronously iterate over a directory. + * It returns a promise that is resolved with an object with two properties: + * - files: an array of nsIURIs corresponding to files that match the extensions passed + * - subdirs: an array of paths for subdirectories we need to recurse into + * (handled by generateURIsFromDirTree above) + * + * @param path the path to check (string) + * @param extensions the file extensions we're interested in. + */ +function iterateOverPath(path, extensions) { + let iterator = new OS.File.DirectoryIterator(path); + let parentDir = new LocalFile(path); + let subdirs = []; + let files = []; + + let pathEntryIterator = (entry) => { + if (entry.isDir) { + subdirs.push(entry.path); + } else if (extensions.some((extension) => entry.name.endsWith(extension))) { + let file = parentDir.clone(); + file.append(entry.name); + // the build system might leave dead symlinks hanging around, which are + // returned as part of the directory iterator, but don't actually exist: + if (file.exists()) { + let uriSpec = getURLForFile(file); + files.push(Services.io.newURI(uriSpec, null, null)); + } + } else if (entry.name.endsWith(".ja") || entry.name.endsWith(".jar") || + entry.name.endsWith(".zip") || entry.name.endsWith(".xpi")) { + let file = parentDir.clone(); + file.append(entry.name); + for (let extension of extensions) { + let jarEntryIterator = generateEntriesFromJarFile(file, extension); + files.push(...jarEntryIterator); + } + } + }; + + return new Promise((resolve, reject) => { + Task.spawn(function* () { + try { + // Iterate through the directory + yield iterator.forEach(pathEntryIterator); + resolve({files: files, subdirs: subdirs}); + } catch (ex) { + reject(ex); + } finally { + iterator.close(); + } + }); + }); +} + +/* Helper function to generate a URI spec (NB: not an nsIURI yet!) + * given an nsIFile object */ +function getURLForFile(file) { + let fileHandler = Services.io.getProtocolHandler("file"); + fileHandler = fileHandler.QueryInterface(Ci.nsIFileProtocolHandler); + return fileHandler.getURLSpecFromActualFile(file); +} + +/** + * A generator that generates nsIURIs for particular files found in jar files + * like omni.ja. + * + * @param jarFile an nsIFile object for the jar file that needs checking. + * @param extension the extension we're interested in. + */ +function* generateEntriesFromJarFile(jarFile, extension) { + let zr = new ZipReader(jarFile); + let entryEnumerator = zr.findEntries("*" + extension + "$"); + + const kURIStart = getURLForFile(jarFile); + while (entryEnumerator.hasMore()) { + let entry = entryEnumerator.getNext(); + // Ignore the JS cache which is stored in omni.ja + if (entry.startsWith("jsloader") || entry.startsWith("jssubloader")) { + continue; + } + let entryURISpec = "jar:" + kURIStart + "!/" + entry; + yield Services.io.newURI(entryURISpec, null, null); + } + zr.close(); +} + + diff --git a/browser/base/content/test/general/permissions.html b/browser/base/content/test/general/permissions.html new file mode 100644 index 000000000..46436a006 --- /dev/null +++ b/browser/base/content/test/general/permissions.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <!-- This page could eventually request permissions from content + and make sure that chrome responds appropriately --> + <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button> + </body> +</html> diff --git a/browser/base/content/test/general/pinning_headers.sjs b/browser/base/content/test/general/pinning_headers.sjs new file mode 100644 index 000000000..51496183a --- /dev/null +++ b/browser/base/content/test/general/pinning_headers.sjs @@ -0,0 +1,23 @@ +const INVALIDPIN1 = "pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\";"; +const INVALIDPIN2 = "pin-sha256=\"AAAAAAAAAAAAAAAAAAAAAAAAAj0e1Md7GkYYkVoZWmM=\";"; +const VALIDPIN = "pin-sha256=\"hXweb81C3HnmM2Ai1dnUzFba40UJMhuu8qZmvN/6WWc=\";"; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + response.setHeader("Content-Type", "text/plain; charset=utf-8", false); + switch (request.queryString) { + case "zeromaxagevalid": + response.setHeader("Public-Key-Pins", "max-age=0;" + VALIDPIN + + INVALIDPIN2 + "includeSubdomains"); + break; + case "valid": + default: + response.setHeader("Public-Key-Pins", "max-age=50000;" + VALIDPIN + + INVALIDPIN2 + "includeSubdomains"); + } + + response.write("Hello world!" + request.host); +} diff --git a/browser/base/content/test/general/print_postdata.sjs b/browser/base/content/test/general/print_postdata.sjs new file mode 100644 index 000000000..4175a2480 --- /dev/null +++ b/browser/base/content/test/general/print_postdata.sjs @@ -0,0 +1,22 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + if (request.method == "GET") { + response.write(request.queryString); + } else { + var body = new BinaryInputStream(request.bodyInputStream); + + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + + var data = String.fromCharCode.apply(null, bytes); + response.bodyOutputStream.write(data, data.length); + } +} diff --git a/browser/base/content/test/general/refresh_header.sjs b/browser/base/content/test/general/refresh_header.sjs new file mode 100644 index 000000000..327372f9b --- /dev/null +++ b/browser/base/content/test/general/refresh_header.sjs @@ -0,0 +1,24 @@ +/** + * Will cause an auto-refresh to the URL provided in the query string + * after some delay using the refresh HTTP header. + * + * Expects the query string to be in the format: + * + * ?p=[URL of the page to redirect to]&d=[delay] + * + * Example: + * + * ?p=http%3A%2F%2Fexample.org%2Fbrowser%2Fbrowser%2Fbase%2Fcontent%2Ftest%2Fgeneral%2Frefresh_meta.sjs&d=200 + */ +function handleRequest(request, response) { + Components.utils.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + + let page = query.get("p"); + let delay = query.get("d"); + + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "200", "Found"); + response.setHeader("refresh", `${delay}; url=${page}`); + response.write("OK"); +}
\ No newline at end of file diff --git a/browser/base/content/test/general/refresh_meta.sjs b/browser/base/content/test/general/refresh_meta.sjs new file mode 100644 index 000000000..648fac1a3 --- /dev/null +++ b/browser/base/content/test/general/refresh_meta.sjs @@ -0,0 +1,36 @@ +/** + * Will cause an auto-refresh to the URL provided in the query string + * after some delay using a <meta> tag. + * + * Expects the query string to be in the format: + * + * ?p=[URL of the page to redirect to]&d=[delay] + * + * Example: + * + * ?p=http%3A%2F%2Fexample.org%2Fbrowser%2Fbrowser%2Fbase%2Fcontent%2Ftest%2Fgeneral%2Frefresh_meta.sjs&d=200 + */ +function handleRequest(request, response) { + Components.utils.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + + let page = query.get("p"); + let delay = query.get("d"); + + let html = `<!DOCTYPE HTML> + <html> + <head> + <meta charset='utf-8'> + <META http-equiv='refresh' content='${delay}; url=${page}'> + <title>Gonna refresh you, folks.</title> + </head> + <body> + <h1>Wait for it...</h1> + </body> + </html>`; + + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "200", "Found"); + response.setHeader("Cache-Control", "no-cache", false); + response.write(html); +}
\ No newline at end of file diff --git a/browser/base/content/test/general/searchSuggestionEngine.sjs b/browser/base/content/test/general/searchSuggestionEngine.sjs new file mode 100644 index 000000000..1978b4f66 --- /dev/null +++ b/browser/base/content/test/general/searchSuggestionEngine.sjs @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function handleRequest(req, resp) { + let suffixes = ["foo", "bar"]; + let data = [req.queryString, suffixes.map(s => req.queryString + s)]; + resp.setHeader("Content-Type", "application/json", false); + resp.write(JSON.stringify(data)); +} diff --git a/browser/base/content/test/general/searchSuggestionEngine.xml b/browser/base/content/test/general/searchSuggestionEngine.xml new file mode 100644 index 000000000..3d1f294f5 --- /dev/null +++ b/browser/base/content/test/general/searchSuggestionEngine.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> + +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName> +<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/> +<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/> +</SearchPlugin> diff --git a/browser/base/content/test/general/searchSuggestionEngine2.xml b/browser/base/content/test/general/searchSuggestionEngine2.xml new file mode 100644 index 000000000..05644649a --- /dev/null +++ b/browser/base/content/test/general/searchSuggestionEngine2.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> + +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_searchSuggestionEngine searchSuggestionEngine2.xml</ShortName> +<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/> +<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine2&terms={searchTerms}" rel="searchform"/> +</SearchPlugin> diff --git a/browser/base/content/test/general/ssl_error_reports.sjs b/browser/base/content/test/general/ssl_error_reports.sjs new file mode 100644 index 000000000..e2e5bafc0 --- /dev/null +++ b/browser/base/content/test/general/ssl_error_reports.sjs @@ -0,0 +1,91 @@ +const EXPECTED_CHAIN = [ + "MIIDCjCCAfKgAwIBAgIENUiGYDANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQxMDAxMjExNDE5WhcNMjQxMDAxMjExNDE5WjAxMS8wLQYDVQQDEyZpbmNsdWRlLXN1YmRvbWFpbnMucGlubmluZy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxYrge8C4eVfTb6/lJ4k/+/4J6wlnWpp5Szxy1MHhsLB+LJh/HRHqkO/tsigT204kTeU3dxuAfQHz0g+Td8dr6KICLLNVFUPw+XjhBV4AtxV8wcprs6EmdBhJgAjkFB4M76BL7/Ow0NfH012WNESn8TTbsp3isgkmrXjTZhWR33vIL1eDNimykp/Os/+JO+x9KVfdCtDCrPwO9Yusial5JiaW7qemRtVuUDL87NSJ7xokPEOSc9luv/fBamZ3rgqf3K6epqg+0o3nNCCcNFnfLW52G0t69+dIjr39WISHnqqZj3Sb7JPU6OmxTd13ByoLkoM3ZUQ2Lpas+RJvQyGXkCAwEAAaM1MDMwMQYDVR0RBCowKIImaW5jbHVkZS1zdWJkb21haW5zLnBpbm5pbmcuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAAmzXfeoOS59FkNABRonFPRyFl7BoGpVJENUteFfTa2pdAhGYdo19Y4uILTTj+vtDAa5yryb5Uvd+YuJnExosbMMkzCrmZ9+VJCJdqUTb+idwk9/sgPl2gtGeRmefB0hXSUFHc/p1CDufSpYOmj9NCUZD2JEsybgJQNulkfAsVnS3lzDcxAwcO+RC/1uJDSiUtcBpWS4FW58liuDYE7PD67kLJHZPVUV2WCMuIl4VM2tKPtvShz1JkZ5UytOLs6jPfviNAk/ftXczaE2/RJgM2MnDX9nGzOxG6ONcVNCljL8avhFBCosutE6i5LYSZR6V14YY/xOn15WDSuWdnIsJCo=", + "MIIC2jCCAcKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQwOTI1MjEyMTU0WhcNMjQwOTI1MjEyMTU0WjAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBT+BwAhO52IWgSIdZZifU9LHOs3IR/+8DCC0WP5d/OuyKlZ6Rqd0tsd3i7durhQyjHSbLf2lJStcnFjcVEbEnNI76RuvlN8xLLn5eV+2Ayr4cZYKztudwRmw+DV/iYAiMSy0hs7m3ssfX7qpoi1aNRjUanwU0VTCPQhF1bEKAC2du+C5Z8e92zN5t87w7bYr7lt+m8197XliXEu+0s9RgnGwGaZ296BIRz6NOoJYTa43n06LU1I1+Z4d6lPdzUFrSR0GBaMhUSurUBtOin3yWiMhg1VHX/KwqGc4als5GyCVXy8HGrA/0zQPOhetxrlhEVAdK/xBt7CZvByj1Rcc7AgMBAAGjEzARMA8GA1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAJq/hogSRqzPWTwX4wTn/DVSNdWwFLv53qep9YrSMJ8ZsfbfK9Es4VP4dBLRQAVMJ0Z5mW1I6d/n0KayTanuUBvemYdxPi/qQNSs8UJcllqdhqWzmzAg6a0LxrMnEeKzPBPD6q8PwQ7tYP+B4sBN9tnnsnyPgti9ZiNZn5FwXZliHXseQ7FE9/SqHlLw5LXW3YtKjuti6RmuV6fq3j+D4oeC5vb1mKgIyoTqGN6ze57v8RHi+pQ8Q+kmoUn/L3Z2YmFe4SKN/4WoyXr8TdejpThGOCGCAd3565s5gOx5QfSQX11P8NZKO8hcN0tme3VzmGpHK0Z/6MTmdpNaTwQ6odk=" + ]; + +const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = -16384; + +function parseReport(request) { + // read the report from the request + let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream); + inputStream.init(request.bodyInputStream, 0x01, 0004, 0); + + let body = ""; + if (inputStream) { + while (inputStream.available()) { + body = body + inputStream.read(inputStream.available()); + } + } + // parse the report + return JSON.parse(body); +} + +function handleRequest(request, response) { + let report = {}; + let certChain = []; + + switch (request.queryString) { + case "succeed": + report = parseReport(request); + certChain = report.failedCertChain; + + // ensure the cert chain is what we expect + for (idx in certChain) { + if (certChain[idx] !== EXPECTED_CHAIN[idx]) { + // if the chain differs, send an error response to cause test + // failure + response.setStatusLine("1.1", 500, "Server error"); + response.write("<html>The report contained an unexpected chain</html>"); + return; + } + } + + if (report.errorCode !== MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) { + response.setStatusLine("1.1", 500, "Server error"); + response.write("<html>The report contained an unexpected error code</html>"); + return; + } + + // if all is as expected, send the 201 the client expects + response.setStatusLine("1.1", 201, "Created"); + response.write("<html>OK</html>"); + break; + case "nocert": + report = parseReport(request); + certChain = report.failedCertChain; + + if (certChain && certChain.length > 0) { + // We're not expecting a chain; if there is one, send an error + response.setStatusLine("1.1", 500, "Server error"); + response.write("<html>The report contained an unexpected chain</html>"); + return; + } + + // if all is as expected, send the 201 the client expects + response.setStatusLine("1.1", 201, "Created"); + response.write("<html>OK</html>"); + break; + case "badcert": + report = parseReport(request); + certChain = report.failedCertChain; + + if (!certChain || certChain.length != 2) { + response.setStatusLine("1.1", 500, "Server error"); + response.write("<html>The report contained an unexpected chain</html>"); + return; + } + + // if all is as expected, send the 201 the client expects + response.setStatusLine("1.1", 201, "Created"); + response.write("<html>OK</html>"); + break; + case "error": + response.setStatusLine("1.1", 500, "Server error"); + response.write("<html>server error</html>"); + break; + default: + response.setStatusLine("1.1", 500, "Server error"); + response.write("<html>succeed, nocert or error expected (got " + request.queryString + ")</html>"); + break; + } +} diff --git a/browser/base/content/test/general/subtst_contextmenu.html b/browser/base/content/test/general/subtst_contextmenu.html new file mode 100644 index 000000000..1768f399f --- /dev/null +++ b/browser/base/content/test/general/subtst_contextmenu.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Subtest for browser context menu</title> +</head> +<body> +Browser context menu subtest. + +<div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div> +<a id="test-link" href="http://mozilla.com">Click the monkey!</a> +<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br> +<input id="test-input"><br> +<img id="test-image" src="ctxmenu-image.png"> +<canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas> +<video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video> +<video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video> +<video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video> +<video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow"> + <source src="bogus.duh" type="video/durrrr;"> +</video> +<iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe> +<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe> +<iframe id="test-audio-in-iframe" src="audio.ogg" width="98" height="98" style="border: 1px solid black"></iframe> +<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe> +<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion --> +<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions --> +<div id="test-contenteditable-spellcheck-false" contenteditable="true" spellcheck="false">test</div> <!-- No Check Spelling menu item --> +<div id="test-dom-full-screen">DOM full screen FTW</div> +<div contextmenu="myMenu"> + <p id="test-pagemenu" hopeless="true">I've got a context menu!</p> + <menu id="myMenu" type="context"> + <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem> + <menuitem label="Disabled item" disabled></menuitem> + <menuitem> Item w/ textContent</menuitem> + <menu> + <menuitem type="checkbox" label="Checkbox" checked></menuitem> + </menu> + <menu> + <menuitem type="radio" label="Radio1" checked></menuitem> + <menuitem type="radio" label="Radio2"></menuitem> + <menuitem type="radio" label="Radio3"></menuitem> + </menu> + <menu> + <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem> + <menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem> + </menu> + <menu label="Submenu"> + <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem> + <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem> + <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem> + <menu> + <menuitem type="checkbox" label="Checkbox"></menuitem> + </menu> + </menu> + <menu hidden> + <menuitem label="Bogus item"></menuitem> + </menu> + <menu> + </menu> + <menuitem label="Hidden item" hidden></menuitem> + <menuitem></menuitem> + </menu> +</div> +<div id="test-select-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div> +<div id="test-select-text-link">http://mozilla.com</div> +<a id="test-image-link" href="#"><img src="ctxmenu-image.png"></a> +<input id="test-select-input-text" type="text" value="input"> +<input id="test-select-input-text-type-password" type="password" value="password"> +<embed id="test-plugin" style="width: 200px; height: 200px;" type="application/x-test"></embed> +<img id="test-longdesc" src="ctxmenu-image.png" longdesc="http://www.mozilla.org"></embed> +<iframe id="test-srcdoc" width="98" height="98" srcdoc="Hello World" style="border: 1px solid black"></iframe> +</body> +</html> diff --git a/browser/base/content/test/general/subtst_contextmenu_input.html b/browser/base/content/test/general/subtst_contextmenu_input.html new file mode 100644 index 000000000..c5be977ea --- /dev/null +++ b/browser/base/content/test/general/subtst_contextmenu_input.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+ Browser context menu subtest.
+ <input id="input_text">
+ <input id="input_spellcheck_no_value">
+ <input id="input_spellcheck_incorrect" spellcheck="true" value="prodkjfgigrty">
+ <input id="input_spellcheck_correct" spellcheck="true" value="foo">
+ <input id="input_disabled" disabled="true">
+ <input id="input_password">
+ <input id="input_email" type="email">
+ <input id="input_tel" type="tel">
+ <input id="input_url" type="url">
+ <input id="input_number" type="number">
+ <input id="input_date" type="date">
+ <input id="input_time" type="time">
+ <input id="input_color" type="color">
+ <input id="input_range" type="range">
+ <input id="input_search" type="search">
+ <input id="input_datetime" type="datetime">
+ <input id="input_month" type="month">
+ <input id="input_week" type="week">
+ <input id="input_datetime-local" type="datetime-local">
+ <input id="input_readonly" readonly="true">
+</body>
+</html>
diff --git a/browser/base/content/test/general/subtst_contextmenu_xul.xul b/browser/base/content/test/general/subtst_contextmenu_xul.xul new file mode 100644 index 000000000..5a2ab42e8 --- /dev/null +++ b/browser/base/content/test/general/subtst_contextmenu_xul.xul @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this file, + - You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <label id="test-xul-text-link-label" class="text-link" value="XUL text-link label" href="https://www.mozilla.com"/> +</page> diff --git a/browser/base/content/test/general/svg_image.html b/browser/base/content/test/general/svg_image.html new file mode 100644 index 000000000..7ab17c33a --- /dev/null +++ b/browser/base/content/test/general/svg_image.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test for page info svg images</title> + </head> + <body> + <svg width="20" height="20"> + <image xlink:href="title_test.svg" width="20" height="20"> + </svg> + </body> +</html> diff --git a/browser/base/content/test/general/test-mixedcontent-securityerrors.html b/browser/base/content/test/general/test-mixedcontent-securityerrors.html new file mode 100644 index 000000000..cb8cfdaaf --- /dev/null +++ b/browser/base/content/test/general/test-mixedcontent-securityerrors.html @@ -0,0 +1,21 @@ +<!-- + Bug 875456 - Log mixed content messages from the Mixed Content Blocker to the + Security Pane in the Web Console +--> + +<!DOCTYPE HTML> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + <title>Mixed Content test - http on https</title> + <script src="testscript.js"></script> + <!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ + --> + </head> + <body> + <iframe src="http://example.com"></iframe> + <img src="http://example.com/tests/image/test/mochitest/blue.png"></img> + </body> +</html> diff --git a/browser/base/content/test/general/test_bug364677.html b/browser/base/content/test/general/test_bug364677.html new file mode 100644 index 000000000..67b9729d1 --- /dev/null +++ b/browser/base/content/test/general/test_bug364677.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=364677 +--> +<head> + <title>Test for Bug 364677</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364677">Mozilla Bug 364677</a> +<p id="display"><iframe id="testFrame" src="bug364677-data.xml"></iframe></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 364677 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "feedHandler", + "Feed served as text/xml without a channel/link should have been sniffed"); +}); +addLoadEvent(SimpleTest.finish); +</script> +</pre> +</body> +</html> + diff --git a/browser/base/content/test/general/test_bug395533.html b/browser/base/content/test/general/test_bug395533.html new file mode 100644 index 000000000..ad6209047 --- /dev/null +++ b/browser/base/content/test/general/test_bug395533.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=395533 +--> +<head> + <title>Test for Bug 395533</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=395533">Mozilla Bug 395533</a> +<p id="display"><iframe id="testFrame" src="bug395533-data.txt"></iframe></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 395533 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Need privs because the feed seems to have an about:feeds principal or some + // such. It's not same-origin with us in any case. + is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "", + "Text got sniffed as a feed?"); +}); +addLoadEvent(SimpleTest.finish); + + + + +</script> +</pre> +</body> +</html> + diff --git a/browser/base/content/test/general/test_bug435035.html b/browser/base/content/test/general/test_bug435035.html new file mode 100644 index 000000000..a6624db15 --- /dev/null +++ b/browser/base/content/test/general/test_bug435035.html @@ -0,0 +1 @@ +<img src="http://example.com/browser/browser/base/content/test/general/moz.png"> diff --git a/browser/base/content/test/general/test_bug462673.html b/browser/base/content/test/general/test_bug462673.html new file mode 100644 index 000000000..d864990e4 --- /dev/null +++ b/browser/base/content/test/general/test_bug462673.html @@ -0,0 +1,18 @@ +<html> +<head> +<script> +var w; +function openIt() { + w = window.open("", "window2"); +} +function closeIt() { + if (w) { + w.close(); + w = null; + } +} +</script> +</head> +<body onload="openIt();" onunload="closeIt();"> +</body> +</html> diff --git a/browser/base/content/test/general/test_bug628179.html b/browser/base/content/test/general/test_bug628179.html new file mode 100644 index 000000000..d35e17a7c --- /dev/null +++ b/browser/base/content/test/general/test_bug628179.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test for closing the Find bar in subdocuments</title> + </head> + <body> + <iframe id=iframe src="http://example.com/" width=320 height=240></iframe> + </body> +</html> + diff --git a/browser/base/content/test/general/test_bug839103.html b/browser/base/content/test/general/test_bug839103.html new file mode 100644 index 000000000..3639d4bda --- /dev/null +++ b/browser/base/content/test/general/test_bug839103.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <title>Document for Bug 839103</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style></style> +</head> +<body> +</body> +</html> diff --git a/browser/base/content/test/general/test_bug959531.html b/browser/base/content/test/general/test_bug959531.html new file mode 100644 index 000000000..e749b198a --- /dev/null +++ b/browser/base/content/test/general/test_bug959531.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test for content page with settings button</title> + </head> + <body> + <button name="settings" id="settings">Settings</button> + </body> +</html> diff --git a/browser/base/content/test/general/test_mcb_double_redirect_image.html b/browser/base/content/test/general/test_mcb_double_redirect_image.html new file mode 100644 index 000000000..1b54774ec --- /dev/null +++ b/browser/base/content/test/general/test_mcb_double_redirect_image.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 7-9 for Bug 1082837 - See file browser_mcb_redirect.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=1082837 +--> +<head> + <meta charset="utf-8"> + <title>Bug 1082837</title> + <script> + function image_loaded() { + document.getElementById("mctestdiv").innerHTML = "image loaded"; + } + function image_blocked() { + document.getElementById("mctestdiv").innerHTML = "image blocked"; + } + </script> +</head> +<body> + <div id="mctestdiv"></div> + <img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_http_sjs" onload="image_loaded()" onerror="image_blocked()" ></image> +</body> +</html> diff --git a/browser/base/content/test/general/test_mcb_redirect.html b/browser/base/content/test/general/test_mcb_redirect.html new file mode 100644 index 000000000..88af791a3 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 418354 - See file browser_mcb_redirect.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=418354 +--> +<head> + <meta charset="utf-8"> + <title>Bug 418354</title> +</head> +<body> + <div id="mctestdiv">script blocked</div> + <script src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?script" ></script> +</body> +</html> diff --git a/browser/base/content/test/general/test_mcb_redirect.js b/browser/base/content/test/general/test_mcb_redirect.js new file mode 100644 index 000000000..48538c940 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect.js @@ -0,0 +1,5 @@ +/* + * Once the mixed content blocker is disabled for the page, this scripts loads + * and updates the text inside the div container. + */ +document.getElementById("mctestdiv").innerHTML = "script executed"; diff --git a/browser/base/content/test/general/test_mcb_redirect.sjs b/browser/base/content/test/general/test_mcb_redirect.sjs new file mode 100644 index 000000000..9a1811dfa --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect.sjs @@ -0,0 +1,22 @@ +function handleRequest(request, response) { + var page = "<!DOCTYPE html><html><body>bug 418354 and bug 1082837</body></html>"; + + if (request.queryString === "script") { + var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.js"; + response.setHeader("Cache-Control", "no-cache", false); + } else if (request.queryString === "image_http") { + var redirect = "http://example.com/tests/image/test/mochitest/blue.png"; + response.setHeader("Cache-Control", "max-age=3600", false); + } else if (request.queryString === "image_redirect_http_sjs") { + var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_https"; + response.setHeader("Cache-Control", "max-age=3600", false); + } else if (request.queryString === "image_redirect_https") { + var redirect = "https://example.com/tests/image/test/mochitest/blue.png"; + response.setHeader("Cache-Control", "max-age=3600", false); + } + + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "302", "Found"); + response.setHeader("Location", redirect, false); + response.write(page); +} diff --git a/browser/base/content/test/general/test_mcb_redirect_image.html b/browser/base/content/test/general/test_mcb_redirect_image.html new file mode 100644 index 000000000..c70cd8987 --- /dev/null +++ b/browser/base/content/test/general/test_mcb_redirect_image.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3-6 for Bug 1082837 - See file browser_mcb_redirect.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=1082837 +--> +<head> + <meta charset="utf-8"> + <title>Bug 1082837</title> + <script> + function image_loaded() { + document.getElementById("mctestdiv").innerHTML = "image loaded"; + } + function image_blocked() { + document.getElementById("mctestdiv").innerHTML = "image blocked"; + } + </script> +</head> +<body> + <div id="mctestdiv"></div> + <img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_http" onload="image_loaded()" onerror="image_blocked()" ></image> +</body> +</html> diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font.css b/browser/base/content/test/general/test_no_mcb_on_http_site_font.css new file mode 100644 index 000000000..68a6954cc --- /dev/null +++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.css @@ -0,0 +1,10 @@ +@font-face { + font-family: testFont; + src: url(http://example.com/browser/devtools/client/fontinspector/test/browser_font.woff); +} +body { + font-family: Arial; +} +div { + font-family: testFont; +} diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font.html b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html new file mode 100644 index 000000000..28a9cb2c0 --- /dev/null +++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 2 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=909920 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 909920</title> + <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font.css" /> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<script type="text/javascript"> + function checkLoadStates() { + var ui = SpecialPowers.wrap(window) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShell) + .securityUI; + + var loadedMixedActive = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT); + is(loadedMixedActive, false, "OK: Should not load mixed active content!"); + + var blockedMixedActive = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + is(blockedMixedActive, false, "OK: Should not block mixed active content!"); + + var loadedMixedDisplay = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + is(loadedMixedDisplay, false, "OK: Should not load mixed display content!"); + + var blockedMixedDisplay = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + is(blockedMixedDisplay, false, "OK: Should not block mixed display content!"); + + var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http font"; + document.getElementById("testDiv").innerHTML = newValue; + } +</script> +</head> +<body onload="checkLoadStates()"> + <div class="testDiv" id="testDiv"> + Testing MCB does not trigger warning/error for an http page with https css that includes http font + </div> +</body> +</html> diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css new file mode 100644 index 000000000..f73b573b4 --- /dev/null +++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css @@ -0,0 +1 @@ +@import url(http://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font.css); diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html new file mode 100644 index 000000000..2b3164902 --- /dev/null +++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=909920 +--> +<head> + <meta charset="utf-8"> + <title>Test 3 for Bug 909920</title> + <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css" /> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<script type="text/javascript"> + function checkLoadStates() { + var ui = SpecialPowers.wrap(window) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShell) + .securityUI; + + var loadedMixedActive = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT); + is(loadedMixedActive, false, "OK: Should not load mixed active content!"); + + var blockedMixedActive = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + is(blockedMixedActive, false, "OK: Should not block mixed active content!"); + + var loadedMixedDisplay = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + is(loadedMixedDisplay, false, "OK: Should not load mixed display content!"); + + var blockedMixedDisplay = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + is(blockedMixedDisplay, false, "OK: Should not block mixed display content!"); + + var newValue = "Verifying MCB does not trigger warning/error for an http page "; + newValue += "with https css that imports another http css which includes http font"; + document.getElementById("testDiv").innerHTML = newValue; + } +</script> +</head> +<body onload="checkLoadStates()"> + <div class="testDiv" id="testDiv"> + Testing MCB does not trigger warning/error for an http page with https css that imports another http css which includes http font + </div> +</body> +</html> diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_img.css b/browser/base/content/test/general/test_no_mcb_on_http_site_img.css new file mode 100644 index 000000000..d045e21ba --- /dev/null +++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.css @@ -0,0 +1,3 @@ +#testDiv { + background: url(http://example.com/tests/image/test/mochitest/blue.png) +} diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_img.html b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html new file mode 100644 index 000000000..741573260 --- /dev/null +++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=909920 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 909920</title> + <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_img.css" /> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<script type="text/javascript"> + function checkLoadStates() { + var ui = SpecialPowers.wrap(window) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShell) + .securityUI; + + var loadedMixedActive = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT); + is(loadedMixedActive, false, "OK: Should not load mixed active content!"); + + var blockedMixedActive = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + is(blockedMixedActive, false, "OK: Should not block mixed active content!"); + + var loadedMixedDisplay = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + is(loadedMixedDisplay, false, "OK: Should not load mixed display content!"); + + var blockedMixedDisplay = ui && + !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + is(blockedMixedDisplay, false, "OK: Should not block mixed display content!"); + + var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http image"; + document.getElementById("testDiv").innerHTML = newValue; + } +</script> +</head> +<body onload="checkLoadStates()"> + <div class="testDiv" id="testDiv"> + Testing MCB does not trigger warning/error for an http page with https css that includes http image + </div> +</body> +</html> diff --git a/browser/base/content/test/general/test_offlineNotification.html b/browser/base/content/test/general/test_offlineNotification.html new file mode 100644 index 000000000..4f78184b4 --- /dev/null +++ b/browser/base/content/test/general/test_offlineNotification.html @@ -0,0 +1,129 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462856 +--> +<head> + <title>Test offline app notification</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="offlineByDefault.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"> +<!-- Load the test frame twice from the same domain, + to make sure we get notifications for both --> +<iframe name="testFrame" src="offlineChild.html"></iframe> +<iframe name="testFrame2" src="offlineChild2.html"></iframe> +<!-- Load from another domain to make sure we get a second allow/deny + notification --> +<iframe name="testFrame3" src="http://example.com/tests/browser/base/content/test/general/offlineChild.html"></iframe> + +<iframe id="eventsTestFrame" src="offlineEvent.html"></iframe> + +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +const Cc = SpecialPowers.Cc; + +var numFinished = 0; + +window.addEventListener("message", function(event) { + is(event.data, "success", "Child was successfully cached."); + + if (++numFinished == 3) { + // Clean up after ourself + var pm = Cc["@mozilla.org/permissionmanager;1"]. + getService(SpecialPowers.Ci.nsIPermissionManager); + var ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService); + var uri1 = ioService.newURI(frames.testFrame.location, null, null); + var uri2 = ioService.newURI(frames.testFrame3.location, null, null); + + var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(SpecialPowers.Ci.nsIScriptSecurityManager); + var principal1 = ssm.createCodebasePrincipal(uri1, {}); + var principal2 = ssm.createCodebasePrincipal(uri2, {}); + + pm.removeFromPrincipal(principal1, "offline-app"); + pm.removeFromPrincipal(principal2, "offline-app"); + + offlineByDefault.reset(); + + SimpleTest.finish(); + } + }, false); + +var count = 0; +var expectedEvent = ""; +function eventHandler(evt) { + ++count; + is(evt.type, expectedEvent, "Wrong event!"); +} + +function testEventHandling() { + var events = [ "checking", + "error", + "noupdate", + "downloading", + "progress", + "updateready", + "cached", + "obsolete"]; + var w = document.getElementById("eventsTestFrame").contentWindow; + var e; + for (var i = 0; i < events.length; ++i) { + count = 0; + expectedEvent = events[i]; + e = w.document.createEvent("event"); + e.initEvent(expectedEvent, true, true); + w.applicationCache["on" + expectedEvent] = eventHandler; + w.applicationCache.addEventListener(expectedEvent, eventHandler, true); + w.applicationCache.dispatchEvent(e); + is(count, 2, "Wrong number events!"); + w.applicationCache["on" + expectedEvent] = null; + w.applicationCache.removeEventListener(expectedEvent, eventHandler, true); + w.applicationCache.dispatchEvent(e); + is(count, 2, "Wrong number events!"); + } + + // Test some random event. + count = 0; + expectedEvent = "foo"; + e = w.document.createEvent("event"); + e.initEvent(expectedEvent, true, true); + w.applicationCache.addEventListener(expectedEvent, eventHandler, true); + w.applicationCache.dispatchEvent(e); + is(count, 1, "Wrong number events!"); + w.applicationCache.removeEventListener(expectedEvent, eventHandler, true); + w.applicationCache.dispatchEvent(e); + is(count, 1, "Wrong number events!"); +} + +function loaded() { + testEventHandling(); + + // Click the notification panel's "Allow" button. This should kick + // off updates, which will eventually lead to getting messages from + // the children. + var wm = SpecialPowers.Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(SpecialPowers.Ci.nsIWindowMediator); + var win = wm.getMostRecentWindow("navigator:browser"); + var panel = win.PopupNotifications.panel; + is(panel.childElementCount, 2, "2 notifications being displayed"); + panel.firstElementChild.button.click(); + + // should have dismissed one of the notifications. + is(panel.childElementCount, 1, "1 notification now being displayed"); + panel.firstElementChild.button.click(); +} + +SimpleTest.waitForFocus(loaded); + +</script> +</pre> +</body> +</html> diff --git a/browser/base/content/test/general/test_offline_gzip.html b/browser/base/content/test/general/test_offline_gzip.html new file mode 100644 index 000000000..a18d6604e --- /dev/null +++ b/browser/base/content/test/general/test_offline_gzip.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=501422 + +When content which was transported over the network with +Content-Type: gzip is added to the offline +cache, it can be fetched from the cache successfully. +--> +<head> + <title>Test gzipped offline resources</title> + <meta charset="utf-8"> +</head> +<body> +<p id="display"> +<iframe name="testFrame" src="gZipOfflineChild.html"></iframe> + +<div id="content" style="display: none"> +</div> +</body> +</html> diff --git a/browser/base/content/test/general/test_process_flags_chrome.html b/browser/base/content/test/general/test_process_flags_chrome.html new file mode 100644 index 000000000..adcbf0340 --- /dev/null +++ b/browser/base/content/test/general/test_process_flags_chrome.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> + +<html> +<body> +<p>chrome: test page</p> +<p><a href="chrome://mochitests/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">chrome</a></p> +<p><a href="chrome://mochitests-any/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">canremote</a></p> +<p><a href="chrome://mochitests-content/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">mustremote</a></p> +</body> +</html> diff --git a/browser/base/content/test/general/test_remoteTroubleshoot.html b/browser/base/content/test/general/test_remoteTroubleshoot.html new file mode 100644 index 000000000..7ba1c5268 --- /dev/null +++ b/browser/base/content/test/general/test_remoteTroubleshoot.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<script> +// This test is run multiple times, once with only strings allowed through the +// WebChannel, and once with objects allowed. This function allows us to handle +// both cases without too much pain. +function makeDetails(object) { + if (window.location.search.indexOf("object") >= 0) { + return object; + } + return JSON.stringify(object) +} +// Add a listener for responses to our remote requests. +window.addEventListener("WebChannelMessageToContent", function (event) { + if (event.detail.id == "remote-troubleshooting") { + // Send what we got back to the test. + var backEvent = new window.CustomEvent("WebChannelMessageToChrome", { + detail: makeDetails({ + id: "test-remote-troubleshooting-backchannel", + message: { + message: event.detail.message, + }, + }), + }); + window.dispatchEvent(backEvent); + // and stick it in our DOM just for good measure/diagnostics. + document.getElementById("troubleshooting").textContent = + JSON.stringify(event.detail.message, null, 2); + } +}); + +// Make a request for the troubleshooting data as we load. +window.onload = function() { + var event = new window.CustomEvent("WebChannelMessageToChrome", { + detail: makeDetails({ + id: "remote-troubleshooting", + message: { + command: "request", + }, + }), + }); + window.dispatchEvent(event); +} +</script> + +<body> + <pre id="troubleshooting"/> +</body> + +</html> diff --git a/browser/base/content/test/general/title_test.svg b/browser/base/content/test/general/title_test.svg new file mode 100644 index 000000000..7638fd5cc --- /dev/null +++ b/browser/base/content/test/general/title_test.svg @@ -0,0 +1,59 @@ +<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>This is a root SVG element's title</title> + <foreignObject> + <html xmlns="http://www.w3.org/1999/xhtml"> + <body> + <svg xmlns="http://www.w3.org/2000/svg" id="svg1"> + <title>This is a non-root SVG element title</title> + </svg> + </body> + </html> + </foreignObject> + <text id="text1" x="10px" y="32px" font-size="24px"> + This contains only <title> + <title> + + + This is a title + + </title> + </text> + <text id="text2" x="10px" y="96px" font-size="24px"> + This contains only <desc> + <desc>This is a desc</desc> + </text> + <text id="text3" x="10px" y="128px" font-size="24px" title="ignored for SVG"> + This contains nothing. + </text> + <a id="link1" xlink:href="#"> + This link contains <title> + <title> + This is a title + </title> + <text id="text4" x="10px" y="192px" font-size="24px"> + </text> + </a> + <a id="link2" xlink:href="#"> + <text x="10px" y="192px" font-size="24px"> + This text contains <title> + <title> + This is a title + </title> + </text> + </a> + <a id="link3" xlink:href="#" xlink:title="This is an xlink:title attribute"> + <text x="10px" y="224px" font-size="24px"> + This link contains <title> & xlink:title attr. + <title>This is a title</title> + </text> + </a> + <a id="link4" xlink:href="#" xlink:title="This is an xlink:title attribute"> + <text x="10px" y="256px" font-size="24px"> + This link contains xlink:title attr. + </text> + </a> + <text id="text5" x="10px" y="160px" font-size="24px" + xlink:title="This is an xlink:title attribute but it isn't on a link" > + This contains nothing. + </text> +</svg> diff --git a/browser/base/content/test/general/trackingPage.html b/browser/base/content/test/general/trackingPage.html new file mode 100644 index 000000000..17f0e459e --- /dev/null +++ b/browser/base/content/test/general/trackingPage.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <iframe src="http://tracking.example.com/"></iframe> + </body> +</html> diff --git a/browser/base/content/test/general/unknownContentType_file.pif b/browser/base/content/test/general/unknownContentType_file.pif new file mode 100644 index 000000000..9353d1312 --- /dev/null +++ b/browser/base/content/test/general/unknownContentType_file.pif @@ -0,0 +1 @@ +Dummy content for unknownContentType_dialog_layout_data.pif diff --git a/browser/base/content/test/general/unknownContentType_file.pif^headers^ b/browser/base/content/test/general/unknownContentType_file.pif^headers^ new file mode 100644 index 000000000..09b22facc --- /dev/null +++ b/browser/base/content/test/general/unknownContentType_file.pif^headers^ @@ -0,0 +1 @@ +Content-Type: application/octet-stream diff --git a/browser/base/content/test/general/video.ogg b/browser/base/content/test/general/video.ogg Binary files differnew file mode 100644 index 000000000..ac7ece351 --- /dev/null +++ b/browser/base/content/test/general/video.ogg diff --git a/browser/base/content/test/general/web_video.html b/browser/base/content/test/general/web_video.html new file mode 100644 index 000000000..467fb0ce1 --- /dev/null +++ b/browser/base/content/test/general/web_video.html @@ -0,0 +1,10 @@ +<html> + <head> + <title>Document with Web Video</title> + </head> + <body> + This document has some web video in it. + <br> + <video src="web_video1.ogv" id="video1"> </video> + </body> +</html> diff --git a/browser/base/content/test/general/web_video1.ogv b/browser/base/content/test/general/web_video1.ogv Binary files differnew file mode 100644 index 000000000..093158432 --- /dev/null +++ b/browser/base/content/test/general/web_video1.ogv diff --git a/browser/base/content/test/general/web_video1.ogv^headers^ b/browser/base/content/test/general/web_video1.ogv^headers^ new file mode 100644 index 000000000..4511e9255 --- /dev/null +++ b/browser/base/content/test/general/web_video1.ogv^headers^ @@ -0,0 +1,3 @@ +Content-Disposition: filename="web-video1-expectedName.ogv" +Content-Type: video/ogg + diff --git a/browser/base/content/test/general/zoom_test.html b/browser/base/content/test/general/zoom_test.html new file mode 100644 index 000000000..bf80490ca --- /dev/null +++ b/browser/base/content/test/general/zoom_test.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=416661 +--> + <head> + <title>Test for zoom setting</title> + + </head> + <body> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=416661">Bug 416661</a> + <p>Site specific zoom settings should not apply to image documents.</p> + </body> +</html> diff --git a/browser/base/content/test/newtab/.eslintrc.js b/browser/base/content/test/newtab/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/newtab/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/newtab/browser.ini b/browser/base/content/test/newtab/browser.ini new file mode 100644 index 000000000..2d14d208d --- /dev/null +++ b/browser/base/content/test/newtab/browser.ini @@ -0,0 +1,55 @@ +[DEFAULT] +skip-if = (os == 'linux') # Bug 1243103, Bug 1243398, etc. +support-files = + head.js + +[browser_newtab_1188015.js] +[browser_newtab_background_captures.js] +[browser_newtab_block.js] +[browser_newtab_bug721442.js] +[browser_newtab_bug722273.js] +skip-if = (os == "mac" && debug) # temporary skip-if due to increase in intermittent failures on Mac debug - bug 1119906 +[browser_newtab_bug723102.js] +[browser_newtab_bug723121.js] +[browser_newtab_bug725996.js] +[browser_newtab_bug734043.js] +[browser_newtab_bug735987.js] +[browser_newtab_bug752841.js] +[browser_newtab_bug765628.js] +[browser_newtab_bug876313.js] +[browser_newtab_bug991111.js] +[browser_newtab_bug991210.js] +[browser_newtab_bug998387.js] +[browser_newtab_bug1145428.js] +[browser_newtab_bug1178586.js] +[browser_newtab_bug1194895.js] +[browser_newtab_bug1271075.js] +[browser_newtab_disable.js] +[browser_newtab_drag_drop.js] +[browser_newtab_drag_drop_ext.js] +# temporary until determine why more intermittent on VM +subsuite = clipboard +[browser_newtab_drop_preview.js] +[browser_newtab_enhanced.js] +[browser_newtab_focus.js] +[browser_newtab_perwindow_private_browsing.js] +[browser_newtab_reportLinkAction.js] +[browser_newtab_reflow_load.js] +support-files = + content-reflows.js +[browser_newtab_search.js] +support-files = + searchEngineNoLogo.xml + searchEngineFavicon.xml + searchEngine1xLogo.xml + searchEngine2xLogo.xml + searchEngine1x2xLogo.xml + ../general/searchSuggestionEngine.xml + ../general/searchSuggestionEngine.sjs +[browser_newtab_sponsored_icon_click.js] +skip-if = true # Bug 1314619 +[browser_newtab_undo.js] +# temporary until determine why more intermittent on VM +subsuite = clipboard +[browser_newtab_unpin.js] +[browser_newtab_update.js] diff --git a/browser/base/content/test/newtab/browser_newtab_1188015.js b/browser/base/content/test/newtab/browser_newtab_1188015.js new file mode 100644 index 000000000..f19aae1b9 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_1188015.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +gDirectorySource = "data:application/json," + JSON.stringify({ + "directory": [{ + url: "http://example1.com/", + enhancedImageURI: "data:image/png;base64,helloWORLD2", + title: "title1", + type: "affiliate", + titleBgColor: "green" + }] +}); + +add_task(function* () { + yield pushPrefs(["browser.newtab.preload", false]); + + // Make the page have a directory link + yield setLinks([]); + yield* addNewTabPageTab(); + + let color = yield performOnCell(0, cell => { + return cell.node.querySelector(".newtab-title").style.backgroundColor; + }); + + is(color, "green", "title bg color is green"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_background_captures.js b/browser/base/content/test/newtab/browser_newtab_background_captures.js new file mode 100644 index 000000000..5e838196e --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verifies that hidden, pre-loaded newtabs don't allow background captures, and + * when unhidden, do allow background captures. + */ + +const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled"; + +add_task(function* () { + let imports = {}; + Cu.import("resource://gre/modules/PageThumbs.jsm", imports); + + // Disable captures. + yield pushPrefs([CAPTURE_PREF, false]); + + // Make sure the thumbnail doesn't exist yet. + let url = "http://example.com/"; + let path = imports.PageThumbsStorage.getFilePathForURL(url); + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(path); + try { + file.remove(false); + } + catch (err) {} + + // Add a top site. + yield setLinks("-1"); + + // We need a handle to a hidden, pre-loaded newtab so we can verify that it + // doesn't allow background captures. Ensure we have a preloaded browser. + gBrowser._createPreloadBrowser(); + + // Wait for the preloaded browser to load. + if (gBrowser._preloadedBrowser.contentDocument.readyState != "complete") { + yield BrowserTestUtils.waitForEvent(gBrowser._preloadedBrowser, "load", true); + } + + // We're now ready to use the preloaded browser. + BrowserOpenTab(); + let tab = gBrowser.selectedTab; + + let thumbnailCreatedPromise = new Promise(resolve => { + // Showing the preloaded tab should trigger thumbnail capture. + Services.obs.addObserver(function onCreate(subj, topic, data) { + if (data != url) + return; + Services.obs.removeObserver(onCreate, "page-thumbnail:create"); + ok(true, "thumbnail created after preloaded tab was shown"); + + resolve(); + }, "page-thumbnail:create", false); + }); + + // Enable captures. + yield pushPrefs([CAPTURE_PREF, false]); + + yield thumbnailCreatedPromise; + + // Test finished! + gBrowser.removeTab(tab); + file.remove(false); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_block.js b/browser/base/content/test/newtab/browser_newtab_block.js new file mode 100644 index 000000000..70656462a --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_block.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +requestLongerTimeout(2); + +/* + * These tests make sure that blocking/removing sites from the grid works + * as expected. Pinned tabs should not be moved. Gaps will be re-filled + * if more sites are available. + */ + +gDirectorySource = "data:application/json," + JSON.stringify({ + "suggested": [{ + url: "http://suggested.com/", + imageURI: "data:image/png;base64,helloWORLD3", + title: "title", + type: "affiliate", + adgroup_name: "test", + frecent_sites: ["example0.com"] + }] +}); + +add_task(function* () { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; + let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; + NewTabUtils.isTopPlacesSite = (site) => false; + + // we remove sites and expect the gaps to be filled as long as there still + // are some sites available + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield customizeNewTabPage("enhanced"); // Toggle enhanced off + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + yield blockCell(4); + yield* checkGrid("0,1,2,3,5,6,7,8,9"); + + yield blockCell(4); + yield* checkGrid("0,1,2,3,6,7,8,9,"); + + yield blockCell(4); + yield* checkGrid("0,1,2,3,7,8,9,,"); + + // we removed a pinned site + yield restore(); + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",1"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1p,2,3,4,5,6,7,8"); + + yield blockCell(1); + yield* checkGrid("0,2,3,4,5,6,7,8,"); + + // we remove the last site on the grid (which is pinned) and expect the gap + // to be re-filled and the new site to be unpinned + yield restore(); + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + setPinnedLinks(",,,,,,,,8"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8p"); + + yield blockCell(8); + yield* checkGrid("0,1,2,3,4,5,6,7,9"); + + // we remove the first site on the grid with the last one pinned. all cells + // but the last one should shift to the left and a new site fades in + yield restore(); + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + setPinnedLinks(",,,,,,,,8"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8p"); + + yield blockCell(0); + yield* checkGrid("1,2,3,4,5,6,7,9,8p"); + + // Test that blocking the targeted site also removes its associated suggested tile + NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; + yield restore(); + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + yield customizeNewTabPage("enhanced"); // Toggle enhanced on + yield* addNewTabPageTab(); + + yield* checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9"); + + yield blockCell(1); + yield* addNewTabPageTab(); + yield* checkGrid("1,2,3,4,5,6,7,8,9"); + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug1145428.js b/browser/base/content/test/newtab/browser_newtab_bug1145428.js new file mode 100644 index 000000000..72fe70212 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that pinning suggested tile results in: + * - making suggested tile a history tile and replacing enhancedImageURI with imageURI + * - upond end of campaign, replaces landing url with baseDomain and switches + * background image to thumbnail + */ + +gDirectorySource = "data:application/json," + JSON.stringify({ + "suggested": [{ + url: "http://example.com/landing/page.html", + imageURI: "data:image/png;base64,helloWORLD3", + enhancedImageURI: "data:image/png;base64,helloWORLD2", + title: "title", + type: "affiliate", + adgroup_name: "example", + frecent_sites: ["example0.com"], + }] +}); + +add_task(function* () { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; + + function getData(cellNum) { + return performOnCell(cellNum, cell => { + if (!cell.site) + return null; + let siteNode = cell.site.node; + return { + type: siteNode.getAttribute("type"), + thumbnail: siteNode.querySelector(".newtab-thumbnail.thumbnail").style.backgroundImage, + enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage, + title: siteNode.querySelector(".newtab-title").textContent, + suggested: siteNode.getAttribute("suggested"), + url: siteNode.querySelector(".newtab-link").getAttribute("href"), + }; + }); + } + + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + // load another newtab since the first may not get suggested tile + yield* addNewTabPageTab(); + yield* checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9"); + // evaluate suggested tile + let tileData = yield getData(0); + is(tileData.type, "affiliate", "unpinned type"); + is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail"); + is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced"); + is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists"); + is(tileData.url, "http://example.com/landing/page.html", "unpinned landing page"); + + // suggested tile should not be pinned + is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), false, "suggested tile is not pinned"); + + // pin suggested tile + let updatedPromise = whenPagesUpdated(); + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser); + yield updatedPromise; + + // tile should be pinned and turned into history tile + is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), true, "suggested tile is pinned"); + tileData = yield getData(0); + is(tileData.type, "history", "pinned type"); + is(tileData.suggested, null, "no suggested attribute"); + is(tileData.url, "http://example.com/landing/page.html", "original landing page"); + + // set pinned tile endTime into past and reload the page + NewTabUtils.pinnedLinks._links[0].endTime = Date.now() - 1000; + yield* addNewTabPageTab(); + + // check that url is reset to base domain and thumbnail points to moz-page-thumb service + is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/"}), true, "baseDomain url is pinned"); + tileData = yield getData(0); + is(tileData.type, "history", "type is history"); + is(tileData.title, "example.com", "title changed to baseDomain"); + is(tileData.thumbnail.indexOf("moz-page-thumb") != -1, true, "thumbnail contains moz-page-thumb"); + is(tileData.enhanced, "", "no enhanced image"); + is(tileData.url, "http://example.com/", "url points to baseDomian"); + + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug1178586.js b/browser/base/content/test/newtab/browser_newtab_bug1178586.js new file mode 100644 index 000000000..84d5cb577 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that pinned suggested tile turns into history tile + * and remains a history tile after a user clicks on it + */ + +gDirectorySource = "data:application/json," + JSON.stringify({ + "suggested": [{ + url: "http://example.com/hardlanding/page.html", + imageURI: "data:image/png;base64,helloWORLD3", + enhancedImageURI: "data:image/png;base64,helloWORLD2", + title: "title", + type: "affiliate", + adgroup_name: "example", + frecent_sites: ["example0.com"], + }] +}); + +add_task(function* () { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; + + function getData(cellNum) { + return performOnCell(cellNum, cell => { + if (!cell.site) + return null; + let siteNode = cell.site.node; + return { + type: siteNode.getAttribute("type"), + thumbnail: siteNode.querySelector(".newtab-thumbnail.thumbnail").style.backgroundImage, + enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage, + title: siteNode.querySelector(".newtab-title").textContent, + suggested: siteNode.getAttribute("suggested"), + url: siteNode.querySelector(".newtab-link").getAttribute("href"), + }; + }); + } + + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + // load another newtab since the first may not get suggested tile + yield* addNewTabPageTab(); + yield* checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9"); + // evaluate suggested tile + let tileData = yield getData(0); + is(tileData.type, "affiliate", "unpinned type"); + is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail"); + is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced"); + is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists"); + is(tileData.url, "http://example.com/hardlanding/page.html", "unpinned landing page"); + + // suggested tile should not be pinned + is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), false, "suggested tile is not pinned"); + + // pin suggested tile + let updatedPromise = whenPagesUpdated(); + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser); + yield updatedPromise; + + // tile should be pinned and turned into history tile + is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), true, "suggested tile is pinned"); + tileData = yield getData(0); + is(tileData.type, "history", "pinned type"); + is(tileData.suggested, null, "no suggested attribute"); + is(tileData.url, "http://example.com/hardlanding/page.html", "original landing page"); + + // click the pinned tile + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser); + // add new page twice to avoid using cached version + yield* addNewTabPageTab(); + yield* addNewTabPageTab(); + + // check that type and suggested did not change + tileData = yield getData(0); + is(tileData.type, "history", "pinned type"); + is(tileData.suggested, null, "no suggested attribute"); + + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug1194895.js b/browser/base/content/test/newtab/browser_newtab_bug1194895.js new file mode 100644 index 000000000..c08b23185 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug1194895.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const PRELOAD_PREF = "browser.newtab.preload"; +const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns"; +const PREF_NEWTAB_ROWS = "browser.newtabpage.rows"; + +function populateDirectoryTiles() { + let directoryTiles = []; + let i = 0; + while (i++ < 14) { + directoryTiles.push({ + directoryId: i, + url: "http://example" + i + ".com/", + enhancedImageURI: "data:image/png;base64,helloWORLD", + title: "dirtitle" + i, + type: "affiliate" + }); + } + return directoryTiles; +} + +gDirectorySource = "data:application/json," + JSON.stringify({ + "directory": populateDirectoryTiles() +}); + + +add_task(function* () { + requestLongerTimeout(4); + let origEnhanced = NewTabUtils.allPages.enhanced; + let origCompareLinks = NewTabUtils.links.compareLinks; + registerCleanupFunction(() => { + NewTabUtils.allPages.enhanced = origEnhanced; + NewTabUtils.links.compareLinks = origCompareLinks; + }); + + // turn off preload to ensure grid updates on every setLinks + yield pushPrefs([PRELOAD_PREF, false]); + // set newtab to have three columns only + yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]); + yield pushPrefs([PREF_NEWTAB_ROWS, 5]); + + yield* addNewTabPageTab(); + yield customizeNewTabPage("enhanced"); // Toggle enhanced off + + // Testing history tiles + + // two rows of tiles should always fit on any screen + yield setLinks("0,1,2,3,4,5"); + yield* addNewTabPageTab(); + + // should do not see scrollbar since tiles fit into visible space + yield* checkGrid("0,1,2,3,4,5"); + let scrolling = yield hasScrollbar(); + ok(!scrolling, "no scrollbar"); + + // add enough tiles to cause extra two rows and observe scrollbar + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8,9"); + scrolling = yield hasScrollbar(); + ok(scrolling, "document has scrollbar"); + + // pin the last tile to make it stay at the bottom of the newtab + yield pinCell(9); + // block first 6 tiles, which should not remove the scroll bar + // since the last tile is pinned in the nineth position + for (let i = 0; i < 6; i++) { + yield blockCell(0); + } + yield* addNewTabPageTab(); + yield* checkGrid("6,7,8,,,,,,,9p"); + scrolling = yield hasScrollbar(); + ok(scrolling, "document has scrollbar when tile is pinned to the last row"); + + // unpin the site: this will move tile up and make scrollbar disappear + yield unpinCell(9); + yield* addNewTabPageTab(); + yield* checkGrid("6,7,8,9"); + scrolling = yield hasScrollbar(); + ok(!scrolling, "no scrollbar when bottom row tile is unpinned"); + + // reset everything to clean slate + NewTabUtils.restore(); + + // Testing directory tiles + yield customizeNewTabPage("enhanced"); // Toggle enhanced on + + // setup page with no history tiles to test directory only display + yield setLinks([]); + yield* addNewTabPageTab(); + ok(!scrolling, "no scrollbar for directory tiles"); + + // introduce one history tile - it should occupy the last + // available slot at the bottom of newtab and cause scrollbar + yield setLinks("41"); + yield* addNewTabPageTab(); + scrolling = yield hasScrollbar(); + ok(scrolling, "adding low frecency history site causes scrollbar"); + + // set PREF_NEWTAB_ROWS to 4, that should clip off the history tile + // and remove scroll bar + yield pushPrefs([PREF_NEWTAB_ROWS, 4]); + yield* addNewTabPageTab(); + + scrolling = yield hasScrollbar(); + ok(!scrolling, "no scrollbar if history tiles falls past max rows"); + + // restore max rows and watch scrollbar re-appear + yield pushPrefs([PREF_NEWTAB_ROWS, 5]); + yield* addNewTabPageTab(); + scrolling = yield hasScrollbar(); + ok(scrolling, "scrollbar is back when max rows allow for bottom history tile"); + + // block that history tile, and watch scrollbar disappear + yield blockCell(14); + yield* addNewTabPageTab(); + scrolling = yield hasScrollbar(); + ok(!scrolling, "no scrollbar after bottom history tiles is blocked"); + + // Test well-populated user history - newtab has highly-frecent history sites + // redefine compareLinks to always choose history tiles first + NewTabUtils.links.compareLinks = function (aLink1, aLink2) { + if (aLink1.type == aLink2.type) { + return aLink2.frecency - aLink1.frecency || + aLink2.lastVisitDate - aLink1.lastVisitDate; + } + if (aLink2.type == "history") { + return 1; + } + return -1; + }; + + // add a row of history tiles, directory tiles will be clipped off, hence no scrollbar + yield setLinks("31,32,33"); + yield* addNewTabPageTab(); + scrolling = yield hasScrollbar(); + ok(!scrolling, "no scrollbar when directory tiles follow history tiles"); + + // fill first four rows with history tiles and observer scrollbar + yield setLinks("30,31,32,33,34,35,36,37,38,39"); + yield* addNewTabPageTab(); + scrolling = yield hasScrollbar(); + ok(scrolling, "scrollbar appears when history tiles need extra row"); +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_bug1271075.js b/browser/base/content/test/newtab/browser_newtab_bug1271075.js new file mode 100644 index 000000000..723b48fc6 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug1271075.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + is(gBrowser.tabs.length, 1, "one tab is open initially"); + + // Add a few tabs. + let tabs = []; + function addTab(aURL, aReferrer) { + let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer}); + tabs.push(tab); + return BrowserTestUtils.browserLoaded(tab.linkedBrowser); + } + + yield addTab("http://mochi.test:8888/#0"); + yield addTab("http://mochi.test:8888/#1"); + yield addTab("http://mochi.test:8888/#2"); + yield addTab("http://mochi.test:8888/#3"); + + // Create a new tab page with a "www.example.com" tile and move it to the 2nd tab position. + yield setLinks("-1"); + yield* addNewTabPageTab(); + gBrowser.moveTabTo(gBrowser.selectedTab, 1); + + // Send a middle-click and confirm that the clicked site opens immediately next to the new tab page. + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell", + {button: 1}, gBrowser.selectedBrowser); + + yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserAtIndex(2)); + is(gBrowser.getBrowserAtIndex(2).currentURI.spec, "http://example.com/", + "Middle click opens site in a new tab immediately to the right."); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug721442.js b/browser/base/content/test/newtab/browser_newtab_bug721442.js new file mode 100644 index 000000000..99bd8d930 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks([ + {url: "http://example7.com/", title: ""}, + {url: "http://example8.com/", title: "title"}, + {url: "http://example9.com/", title: "http://example9.com/"} + ]); + + yield* addNewTabPageTab(); + yield* checkGrid("7p,8p,9p,0,1,2,3,4,5"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + function checkTooltip(aIndex, aExpected, aMessage) { + let cell = content.gGrid.cells[aIndex]; + + let link = cell.node.querySelector(".newtab-link"); + Assert.equal(link.getAttribute("title"), aExpected, aMessage); + } + + checkTooltip(0, "http://example7.com/", "1st tooltip is correct"); + checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct"); + checkTooltip(2, "http://example9.com/", "3rd tooltip is correct"); + }); +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_bug722273.js b/browser/base/content/test/newtab/browser_newtab_bug722273.js new file mode 100644 index 000000000..5cbfcd3ff --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NOW = Date.now() * 1000; +const URL = "http://fake-site.com/"; + +var tmp = {}; +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tmp); + +var {Sanitizer} = tmp; + +add_task(function* () { + yield promiseSanitizeHistory(); + yield promiseAddFakeVisits(); + yield* addNewTabPageTab(); + + let cellUrl = yield performOnCell(0, cell => { return cell.site.url; }); + is(cellUrl, URL, "first site is our fake site"); + + let updatedPromise = whenPagesUpdated(); + yield promiseSanitizeHistory(); + yield updatedPromise; + + let isGone = yield performOnCell(0, cell => { return cell.site == null; }); + ok(isGone, "fake site is gone"); +}); + +function promiseAddFakeVisits() { + let visits = []; + for (let i = 59; i > 0; i--) { + visits.push({ + visitDate: NOW - i * 60 * 1000000, + transitionType: Ci.nsINavHistoryService.TRANSITION_LINK + }); + } + let place = { + uri: makeURI(URL), + title: "fake site", + visits: visits + }; + return new Promise((resolve, reject) => { + PlacesUtils.asyncHistory.updatePlaces(place, { + handleError: () => reject(new Error("Couldn't add visit")), + handleResult: function () {}, + handleCompletion: function () { + NewTabUtils.links.populateCache(function () { + NewTabUtils.allPages.update(); + resolve(); + }, true); + } + }); + }); +} + +function promiseSanitizeHistory() { + let s = new Sanitizer(); + s.prefDomain = "privacy.cpd."; + + let prefs = gPrefService.getBranch(s.prefDomain); + prefs.setBoolPref("history", true); + prefs.setBoolPref("downloads", false); + prefs.setBoolPref("cache", false); + prefs.setBoolPref("cookies", false); + prefs.setBoolPref("formdata", false); + prefs.setBoolPref("offlineApps", false); + prefs.setBoolPref("passwords", false); + prefs.setBoolPref("sessions", false); + prefs.setBoolPref("siteSettings", false); + + return s.sanitize(); +} diff --git a/browser/base/content/test/newtab/browser_newtab_bug723102.js b/browser/base/content/test/newtab/browser_newtab_bug723102.js new file mode 100644 index 000000000..02282dc97 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + // create a new tab page and hide it. + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + let firstTab = gBrowser.selectedTab; + + yield* addNewTabPageTab(); + yield BrowserTestUtils.removeTab(firstTab); + + ok(NewTabUtils.allPages.enabled, "page is enabled"); + NewTabUtils.allPages.enabled = false; + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + Assert.ok(content.gGrid.node.hasAttribute("page-disabled"), "page is disabled"); + }); + + NewTabUtils.allPages.enabled = true; +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_bug723121.js b/browser/base/content/test/newtab/browser_newtab_bug723121.js new file mode 100644 index 000000000..82f45ebd5 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() { + let grid = content.gGrid; + let cell = grid.cells[0]; + let site = cell.site.node; + let link = site.querySelector(".newtab-link"); + + function checkGridLocked(aLocked, aMessage) { + Assert.equal(grid.node.hasAttribute("locked"), aLocked, aMessage); + } + + function sendDragEvent(aEventType, aTarget) { + let dataTransfer = new content.DataTransfer(aEventType, false); + let event = content.document.createEvent("DragEvent"); + event.initDragEvent(aEventType, true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + aTarget.dispatchEvent(event); + } + + checkGridLocked(false, "grid is unlocked"); + + sendDragEvent("dragstart", link); + checkGridLocked(true, "grid is now locked"); + + sendDragEvent("dragend", link); + checkGridLocked(false, "grid isn't locked anymore"); + + sendDragEvent("dragstart", cell.node); + checkGridLocked(false, "grid isn't locked - dragstart was ignored"); + + sendDragEvent("dragstart", site); + checkGridLocked(false, "grid isn't locked - dragstart was ignored"); + }); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug725996.js b/browser/base/content/test/newtab/browser_newtab_bug725996.js new file mode 100644 index 000000000..e0de809c8 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + function doDrop(data) { + return ContentTask.spawn(gBrowser.selectedBrowser, { data: data }, function*(args) { + let dataTransfer = new content.DataTransfer("dragstart", false); + dataTransfer.mozSetDataAt("text/x-moz-url", args.data, 0); + let event = content.document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + + let target = content.gGrid.cells[0].node; + target.dispatchEvent(event); + }); + } + + yield doDrop("http://example99.com/\nblank"); + is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/", + "first cell is pinned and contains the dropped site"); + + yield whenPagesUpdated(); + yield* checkGrid("99p,0,1,2,3,4,5,6,7"); + + yield doDrop(""); + is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/", + "first cell is still pinned with the site we dropped before"); +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_bug734043.js b/browser/base/content/test/newtab/browser_newtab_bug734043.js new file mode 100644 index 000000000..02f765274 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + content.addEventListener("error", function () { + sendAsyncMessage("test:newtab-error", {}); + }); + }); + + let receivedError = false; + let mm = gBrowser.selectedBrowser.messageManager; + mm.addMessageListener("test:newtab-error", function onResponse(message) { + mm.removeMessageListener("test:newtab-error", onResponse); + ok(false, "Error event happened"); + receivedError = true; + }); + + let pagesUpdatedPromise = whenPagesUpdated(); + + for (let i = 0; i < 3; i++) { + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block", {}, gBrowser.selectedBrowser); + } + + yield pagesUpdatedPromise; + + ok(!receivedError, "we got here without any errors"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug735987.js b/browser/base/content/test/newtab/browser_newtab_bug735987.js new file mode 100644 index 000000000..2ae541c70 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + yield* simulateExternalDrop(1); + yield* checkGrid("0,99p,1,2,3,4,5,6,7"); + + yield blockCell(1); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + yield* simulateExternalDrop(1); + yield* checkGrid("0,99p,1,2,3,4,5,6,7"); + + // Simulate a restart and force the next about:newtab + // instance to read its data from the storage again. + NewTabUtils.blockedLinks.resetCache(); + + // Update all open pages, e.g. preloaded ones. + NewTabUtils.allPages.update(); + + yield* addNewTabPageTab(); + yield* checkGrid("0,99p,1,2,3,4,5,6,7"); + + yield blockCell(1); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug752841.js b/browser/base/content/test/newtab/browser_newtab_bug752841.js new file mode 100644 index 000000000..e3faad13f --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug752841.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const PREF_NEWTAB_ROWS = "browser.newtabpage.rows"; +const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns"; + +function getCellsCount() +{ + return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + return content.gGrid.cells.length; + }); +} + +add_task(function* () { + let testValues = [ + {row: 0, column: 0}, + {row: -1, column: -1}, + {row: -1, column: 0}, + {row: 0, column: -1}, + {row: 2, column: 4}, + {row: 2, column: 5}, + ]; + + // Expected length of grid + let expectedValues = [1, 1, 1, 1, 8, 10]; + + // Values before setting new pref values (15 is the default value -> 5 x 3) + let previousValues = [15, 1, 1, 1, 1, 8]; + + yield* addNewTabPageTab(); + let existingTab = gBrowser.selectedTab; + + for (let i = 0; i < expectedValues.length; i++) { + let existingTabGridLength = yield getCellsCount(); + is(existingTabGridLength, previousValues[i], + "Grid length of existing page before update is correctly."); + + yield pushPrefs([PREF_NEWTAB_ROWS, testValues[i].row]); + yield pushPrefs([PREF_NEWTAB_COLUMNS, testValues[i].column]); + + existingTabGridLength = yield getCellsCount(); + is(existingTabGridLength, expectedValues[i], + "Existing page grid is updated correctly."); + + yield* addNewTabPageTab(); + let newTab = gBrowser.selectedTab; + let newTabGridLength = yield getCellsCount(); + is(newTabGridLength, expectedValues[i], + "New page grid is updated correctly."); + + yield BrowserTestUtils.removeTab(newTab); + } + + gBrowser.removeTab(existingTab); +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_bug765628.js b/browser/base/content/test/newtab/browser_newtab_bug765628.js new file mode 100644 index 000000000..25afd32a9 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield checkGrid("0,1,2,3,4,5,6,7,8"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() { + const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff"; + const GOOD_DRAG_DATA = "http://example99.com/\nsite 99"; + + function sendDropEvent(aCellIndex, aDragData) { + let dataTransfer = new content.DataTransfer("dragstart", false); + dataTransfer.mozSetDataAt("text/x-moz-url", aDragData, 0); + let event = content.document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + + let target = content.gGrid.cells[aCellIndex].node; + target.dispatchEvent(event); + } + + sendDropEvent(0, BAD_DRAG_DATA); + sendDropEvent(1, GOOD_DRAG_DATA); + }); + + yield whenPagesUpdated(); + yield* checkGrid("0,99p,1,2,3,4,5,6,7"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug876313.js b/browser/base/content/test/newtab/browser_newtab_bug876313.js new file mode 100644 index 000000000..1c0b0f501 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug876313.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * This test makes sure that the changes made by unpinning + * a site are actually written to NewTabUtils' storage. + */ +add_task(function* () { + // Second cell is pinned with page #99. + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",99"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,99p,1,2,3,4,5,6,7"); + + // Unpin the second cell's site. + yield unpinCell(1); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + // Clear the pinned cache to force NewTabUtils to read the pref again. + NewTabUtils.pinnedLinks.resetCache(); + NewTabUtils.allPages.update(); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug991111.js b/browser/base/content/test/newtab/browser_newtab_bug991111.js new file mode 100644 index 000000000..37aa8213b --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + // set max rows to 1, to avoid scroll events by clicking middle button + yield pushPrefs(["browser.newtabpage.rows", 1]); + yield setLinks("-1"); + yield* addNewTabPageTab(); + // we need a second newtab to honor max rows + yield* addNewTabPageTab(); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) { + let {site} = content.wrappedJSObject.gGrid.cells[args.index]; + + let origOnClick = site.onClick; + site.onClick = e => { + origOnClick.call(site, e); + sendAsyncMessage("test:clicked-on-cell", {}); + }; + }); + + let mm = gBrowser.selectedBrowser.messageManager; + let messagePromise = new Promise(resolve => { + mm.addMessageListener("test:clicked-on-cell", function onResponse(message) { + mm.removeMessageListener("test:clicked-on-cell", onResponse); + resolve(); + }); + }); + + // Send a middle-click and make sure it happened + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell", + {button: 1}, gBrowser.selectedBrowser); + yield messagePromise; + ok(true, "middle click triggered click listener"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug991210.js b/browser/base/content/test/newtab/browser_newtab_bug991210.js new file mode 100644 index 000000000..367c49f5c --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug991210.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + // turn off preload to ensure that a newtab page loads + yield pushPrefs(["browser.newtab.preload", false]); + + // add a test provider that waits for load + let afterLoadProvider = { + getLinks: function(callback) { + this.callback = callback; + }, + addObserver: function() {}, + }; + NewTabUtils.links.addProvider(afterLoadProvider); + + // wait until about:newtab loads before calling provider callback + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab"); + + afterLoadProvider.callback([]); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let {_cellHeight, _cellWidth, node} = content.gGrid; + Assert.notEqual(_cellHeight, null, "grid has a computed cell height"); + Assert.notEqual(_cellWidth, null, "grid has a computed cell width"); + let {height, maxHeight, maxWidth} = node.style; + Assert.notEqual(height, "", "grid has a computed grid height"); + Assert.notEqual(maxHeight, "", "grid has a computed grid max-height"); + Assert.notEqual(maxWidth, "", "grid has a computed grid max-width"); + }); + + // restore original state + NewTabUtils.links.removeProvider(afterLoadProvider); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_bug998387.js b/browser/base/content/test/newtab/browser_newtab_bug998387.js new file mode 100644 index 000000000..30424c2e5 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug998387.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + // set max rows to 1, to avoid scroll events by clicking middle button + yield pushPrefs(["browser.newtabpage.rows", 1]); + yield setLinks("0"); + yield* addNewTabPageTab(); + // we need a second newtab to honor max rows + yield* addNewTabPageTab(); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) { + let {site} = content.wrappedJSObject.gGrid.cells[args.index]; + + let origOnClick = site.onClick; + site.onClick = e => { + origOnClick.call(site, e); + sendAsyncMessage("test:clicked-on-cell", {}); + }; + }); + + let mm = gBrowser.selectedBrowser.messageManager; + let messagePromise = new Promise(resolve => { + mm.addMessageListener("test:clicked-on-cell", function onResponse(message) { + mm.removeMessageListener("test:clicked-on-cell", onResponse); + resolve(); + }); + }); + + // Send a middle-click and make sure it happened + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block", + {button: 1}, gBrowser.selectedBrowser); + + yield messagePromise; + ok(true, "middle click triggered click listener"); + + // Make sure the cell didn't actually get blocked + yield* checkGrid("0"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_disable.js b/browser/base/content/test/newtab/browser_newtab_disable.js new file mode 100644 index 000000000..58b9a18af --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_disable.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that the 'New Tab Page' feature can be disabled if the + * decides not to use it. + */ +add_task(function* () { + // create a new tab page and hide it. + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + let firstTab = yield* addNewTabPageTab(); + + function isGridDisabled(browser = gBrowser.selectedBrowser) + { + return ContentTask.spawn(browser, {}, function*() { + return content.gGrid.node.hasAttribute("page-disabled"); + }); + } + + let isDisabled = yield isGridDisabled(); + ok(!isDisabled, "page is not disabled"); + + NewTabUtils.allPages.enabled = false; + + isDisabled = yield isGridDisabled(); + ok(isDisabled, "page is disabled"); + + // create a second new tab page and make sure it's disabled. enable it + // again and check if the former page gets enabled as well. + yield* addNewTabPageTab(); + isDisabled = yield isGridDisabled(firstTab.linkedBrowser); + ok(isDisabled, "page is disabled"); + + // check that no sites have been rendered + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() { + Assert.equal(content.document.querySelectorAll(".site").length, 0, + "no sites have been rendered"); + }); + + NewTabUtils.allPages.enabled = true; + + isDisabled = yield isGridDisabled(); + ok(!isDisabled, "page is not disabled"); + + isDisabled = yield isGridDisabled(firstTab.linkedBrowser); + ok(!isDisabled, "old page is not disabled"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop.js b/browser/base/content/test/newtab/browser_newtab_drag_drop.js new file mode 100644 index 000000000..da9d89de7 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that dragging and dropping sites works as expected. + * Sites contained in the grid need to shift around to indicate the result + * of the drag-and-drop operation. If the grid is full and we're dragging + * a new site into it another one gets pushed out. + */ +add_task(function* () { + requestLongerTimeout(2); + yield* addNewTabPageTab(); + + // test a simple drag-and-drop scenario + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + yield doDragEvent(0, 1); + yield* checkGrid("1,0p,2,3,4,5,6,7,8"); + + // drag a cell to its current cell and make sure it's not pinned afterwards + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + yield doDragEvent(0, 0); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + // ensure that pinned pages aren't moved if that's not necessary + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",1,2"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1p,2p,3,4,5,6,7,8"); + + yield doDragEvent(0, 3); + yield* checkGrid("3,1p,2p,0p,4,5,6,7,8"); + + // pinned sites should always be moved around as blocks. if a pinned site is + // moved around, neighboring pinned are affected as well + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks("0,1"); + + yield* addNewTabPageTab(); + yield* checkGrid("0p,1p,2,3,4,5,6,7,8"); + + yield doDragEvent(2, 0); + yield* checkGrid("2p,0p,1p,3,4,5,6,7,8"); + + // pinned sites should not be pushed out of the grid (unless there are only + // pinned ones left on the grid) + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",,,,,,,7,8"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7p,8p"); + + yield doDragEvent(2, 5); + yield* checkGrid("0,1,3,4,5,2p,6,7p,8p"); + + // make sure that pinned sites are re-positioned correctly + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks("0,1,2,,,5"); + + yield* addNewTabPageTab(); + yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8"); + + yield doDragEvent(0, 4); + yield* checkGrid("3,1p,2p,4,0p,5p,6,7,8"); +}); + +function doDragEvent(sourceIndex, dropIndex) { + return ContentTask.spawn(gBrowser.selectedBrowser, + { sourceIndex: sourceIndex, dropIndex: dropIndex }, function*(args) { + let dataTransfer = new content.DataTransfer("dragstart", false); + let event = content.document.createEvent("DragEvent"); + event.initDragEvent("dragstart", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + + let target = content.gGrid.cells[args.sourceIndex].site.node; + target.dispatchEvent(event); + + event = content.document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + + target = content.gGrid.cells[args.dropIndex].node; + target.dispatchEvent(event); + }); +} diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js new file mode 100644 index 000000000..4e7b062cb --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +requestLongerTimeout(2); + +const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns"; + +/* + * These tests make sure that dragging and dropping sites works as expected. + * Sites contained in the grid need to shift around to indicate the result + * of the drag-and-drop operation. If the grid is full and we're dragging + * a new site into it another one gets pushed out. + * This is a continuation of browser_newtab_drag_drop.js + * to decrease test run time, focusing on external sites. + */ + add_task(function* () { + yield* addNewTabPageTab(); + + // drag a new site onto the very first cell + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",,,,,,,7,8"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7p,8p"); + + yield* simulateExternalDrop(0); + yield* checkGrid("99p,0,1,2,3,4,5,7p,8p"); + + // drag a new site onto the grid and make sure that pinned cells don't get + // pushed out + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",,,,,,,7,8"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7p,8p"); + + // force the grid to be small enough that a pinned cell could be pushed out + yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]); + yield* simulateExternalDrop(5); + yield* checkGrid("0,1,2,3,4,99p,5,7p,8p"); + + // drag a new site beneath a pinned cell and make sure the pinned cell is + // not moved + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",,,,,,,,8"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1,2,3,4,5,6,7,8p"); + + yield* simulateExternalDrop(5); + yield* checkGrid("0,1,2,3,4,99p,5,6,8p"); + + // drag a new site onto a block of pinned sites and make sure they're shifted + // around accordingly + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks("0,1,2,,,,,,"); + + yield* addNewTabPageTab(); + yield* checkGrid("0p,1p,2p"); + + yield* simulateExternalDrop(1); + yield* checkGrid("0p,99p,1p,2p,3,4,5,6,7"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_drop_preview.js b/browser/base/content/test/newtab/browser_newtab_drop_preview.js new file mode 100644 index 000000000..f9e37f629 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests ensure that the drop preview correctly arranges sites when + * dragging them around. + */ +add_task(function* () { + yield* addNewTabPageTab(); + + // the first three sites are pinned - make sure they're re-arranged correctly + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks("0,1,2,,,5"); + + yield* addNewTabPageTab(); + yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8"); + + let foundSites = yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() { + let cells = content.gGrid.cells; + content.gDrag._draggedSite = cells[0].site; + let sites = content.gDropPreview.rearrange(cells[4]); + content.gDrag._draggedSite = null; + + sites = sites.slice(0, 9); + return sites.map(function (aSite) { + if (!aSite) + return ""; + + let pinned = aSite.isPinned(); + if (pinned != aSite.node.hasAttribute("pinned")) { + Assert.ok(false, "invalid state (site.isPinned() != site[pinned])"); + } + + return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : ""); + }); + }); + + let expectedSites = "3,1p,2p,4,0p,5p,6,7,8" + is(foundSites, expectedSites, "grid status = " + expectedSites); +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_enhanced.js b/browser/base/content/test/newtab/browser_newtab_enhanced.js new file mode 100644 index 000000000..5ac07ce55 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js @@ -0,0 +1,228 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +requestLongerTimeout(2); + +const PRELOAD_PREF = "browser.newtab.preload"; + +var suggestedLink = { + url: "http://example1.com/2", + imageURI: "data:image/png;base64,helloWORLD3", + title: "title2", + type: "affiliate", + adgroup_name: "Technology", + frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"] +}; + +gDirectorySource = "data:application/json," + JSON.stringify({ + "enhanced": [{ + url: "http://example.com/", + enhancedImageURI: "data:image/png;base64,helloWORLD", + title: "title", + type: "organic", + }], + "directory": [{ + url: "http://example1.com/", + enhancedImageURI: "data:image/png;base64,helloWORLD2", + title: "title1", + type: "organic" + }], + "suggested": [suggestedLink] +}); + +add_task(function* () { + let origEnhanced = NewTabUtils.allPages.enhanced; + registerCleanupFunction(() => { + NewTabUtils.allPages.enhanced = origEnhanced; + }); + + yield pushPrefs([PRELOAD_PREF, false]); + + function getData(cellNum) { + return performOnCell(cellNum, cell => { + if (!cell.site) + return null; + let siteNode = cell.site.node; + return { + type: siteNode.getAttribute("type"), + enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage, + title: siteNode.querySelector(".newtab-title").textContent, + suggested: siteNode.querySelector(".newtab-suggested").innerHTML + }; + }); + } + + // Make the page have a directory link, enhanced link, and history link + yield setLinks("-1"); + + // Test with enhanced = false + yield* addNewTabPageTab(); + yield customizeNewTabPage("classic"); + yield customizeNewTabPage("enhanced"); // Toggle enhanced off + let {type, enhanced, title, suggested} = yield getData(0); + isnot(type, "enhanced", "history link is not enhanced"); + is(enhanced, "", "history link has no enhanced image"); + is(title, "example.com"); + is(suggested, "", "There is no suggested explanation"); + + let data = yield getData(1); + is(data, null, "there is only one link and it's a history link"); + + // Test with enhanced = true + yield* addNewTabPageTab(); + yield customizeNewTabPage("enhanced"); // Toggle enhanced on + ({type, enhanced, title, suggested} = yield getData(0)); + is(type, "organic", "directory link is organic"); + isnot(enhanced, "", "directory link has enhanced image"); + is(title, "title1"); + is(suggested, "", "There is no suggested explanation"); + + ({type, enhanced, title, suggested} = yield getData(1)); + is(type, "enhanced", "history link is enhanced"); + isnot(enhanced, "", "history link has enhanced image"); + is(title, "title"); + is(suggested, "", "There is no suggested explanation"); + + data = yield getData(2); + is(data, null, "there are only 2 links, directory and enhanced history"); + + // Test with a pinned link + setPinnedLinks("-1"); + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + is(type, "enhanced", "pinned history link is enhanced"); + isnot(enhanced, "", "pinned history link has enhanced image"); + is(title, "title"); + is(suggested, "", "There is no suggested explanation"); + + ({type, enhanced, title, suggested} = yield getData(1)); + is(type, "organic", "directory link is organic"); + isnot(enhanced, "", "directory link has enhanced image"); + is(title, "title1"); + is(suggested, "", "There is no suggested explanation"); + + data = yield getData(2); + is(data, null, "directory link pushed out by pinned history link"); + + // Test pinned link with enhanced = false + yield* addNewTabPageTab(); + yield customizeNewTabPage("enhanced"); // Toggle enhanced off + ({type, enhanced, title, suggested} = yield getData(0)); + isnot(type, "enhanced", "history link is not enhanced"); + is(enhanced, "", "history link has no enhanced image"); + is(title, "example.com"); + is(suggested, "", "There is no suggested explanation"); + + data = yield getData(1); + is(data, null, "directory link still pushed out by pinned history link"); + + yield unpinCell(0); + + + + // Test that a suggested tile is not enhanced by a directory tile + NewTabUtils.isTopPlacesSite = () => true; + yield setLinks("-1,2,3,4,5,6,7,8"); + + // Test with enhanced = false + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + isnot(type, "enhanced", "history link is not enhanced"); + is(enhanced, "", "history link has no enhanced image"); + is(title, "example.com"); + is(suggested, "", "There is no suggested explanation"); + + data = yield getData(7); + isnot(data, null, "there are 8 history links"); + data = yield getData(8); + is(data, null, "there are 8 history links"); + + + // Test with enhanced = true + yield* addNewTabPageTab(); + yield customizeNewTabPage("enhanced"); + + // Suggested link was not enhanced by directory link with same domain + ({type, enhanced, title, suggested} = yield getData(0)); + is(type, "affiliate", "suggested link is affiliate"); + is(enhanced, "", "suggested link has no enhanced image"); + is(title, "title2"); + ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'"); + + // Enhanced history link shows up second + ({type, enhanced, title, suggested} = yield getData(1)); + is(type, "enhanced", "pinned history link is enhanced"); + isnot(enhanced, "", "pinned history link has enhanced image"); + is(title, "title"); + is(suggested, "", "There is no suggested explanation"); + + data = yield getData(9); + is(data, null, "there is a suggested link followed by an enhanced history link and the remaining history links"); + + + + // Test no override category/adgroup name. + let linksChangedPromise = watchLinksChangeOnce(); + yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE, + "data:application/json," + JSON.stringify({"suggested": [suggestedLink]})]); + yield linksChangedPromise; + + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + Cu.reportError("SUGGEST " + suggested); + ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'"); + + + // Test server provided explanation string. + suggestedLink.explanation = "Suggested for %1$S enthusiasts who visit sites like %2$S"; + linksChangedPromise = watchLinksChangeOnce(); + yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE, + "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]); + yield linksChangedPromise; + + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + Cu.reportError("SUGGEST " + suggested); + ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts"); + + + // Test server provided explanation string with category override. + suggestedLink.adgroup_name = "webdev education"; + linksChangedPromise = watchLinksChangeOnce(); + yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE, + "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]); + yield linksChangedPromise; + + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + Cu.reportError("SUGGEST " + suggested); + ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts"); + + + + // Test with xml entities in category name + suggestedLink.url = "http://example1.com/3"; + suggestedLink.adgroup_name = ">angles< & \"quotes\'"; + linksChangedPromise = watchLinksChangeOnce(); + yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE, + "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]); + yield linksChangedPromise; + + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + Cu.reportError("SUGGEST " + suggested); + ok(suggested.indexOf("Suggested for <strong> >angles< & \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts"); + + + // Test with xml entities in explanation. + suggestedLink.explanation = "Testing junk explanation &<>\"'"; + linksChangedPromise = watchLinksChangeOnce(); + yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE, + "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]); + yield linksChangedPromise; + + yield* addNewTabPageTab(); + ({type, enhanced, title, suggested} = yield getData(0)); + Cu.reportError("SUGGEST " + suggested); + ok(suggested.indexOf("Testing junk explanation &<>\"'") > -1, "Junk test"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_focus.js b/browser/base/content/test/newtab/browser_newtab_focus.js new file mode 100644 index 000000000..ae0dd8d29 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_focus.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that focusing the 'New Tab Page' works as expected. + */ +add_task(function* () { + yield pushPrefs(["accessibility.tabfocus", 7]); + + // Focus count in new tab page. + // 30 = 9 * 3 + 3 = 9 sites, each with link, pin and remove buttons; search + // bar; search button; and toggle button. Additionaly there may or may not be + // a scroll bar caused by fix to 1180387, which will eat an extra focus + let FOCUS_COUNT = 30; + + // Create a new tab page. + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield* addNewTabPageTab(); + gURLBar.focus(); + + // Count the focus with the enabled page. + countFocus(FOCUS_COUNT); + + // Disable page and count the focus with the disabled page. + NewTabUtils.allPages.enabled = false; + + countFocus(4); + + NewTabUtils.allPages.enabled = true; +}); + +/** + * Focus the urlbar and count how many focus stops to return again to the urlbar. + */ +function countFocus(aExpectedCount) { + let focusCount = 0; + do { + EventUtils.synthesizeKey("VK_TAB", {}); + if (document.activeElement == gBrowser.selectedBrowser) { + focusCount++; + } + } while (document.activeElement != gURLBar.inputField); + + ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1), + "Validate focus count in the new tab page."); +} diff --git a/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js new file mode 100644 index 000000000..b330bec13 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests ensure that all changes made to the new tab page in private + * browsing mode are discarded after switching back to normal mode again. + * The private browsing mode should start with the current grid shown in normal + * mode. + */ + +add_task(function* () { + // prepare the grid + yield testOnWindow(undefined); + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + + yield* addNewTabPageTab(); + yield pinCell(0); + yield* checkGrid("0p,1,2,3,4,5,6,7,8"); + + // open private window + yield testOnWindow({private: true}); + + yield* addNewTabPageTab(); + yield* checkGrid("0p,1,2,3,4,5,6,7,8"); + + // modify the grid while we're in pb mode + yield blockCell(1); + yield* checkGrid("0p,2,3,4,5,6,7,8"); + + yield unpinCell(0); + yield* checkGrid("0,2,3,4,5,6,7,8"); + + // open normal window + yield testOnWindow(undefined); + + // check that the grid is the same as before entering pb mode + yield* addNewTabPageTab(); + yield* checkGrid("0,2,3,4,5,6,7,8") +}); + +var windowsToClose = []; +function* testOnWindow(options) { + let newWindowPromise = BrowserTestUtils.waitForNewWindow(); + var win = OpenBrowserWindow(options); + windowsToClose.push(win); + gWindow = win; + yield newWindowPromise; +} + +registerCleanupFunction(function () { + gWindow = window; + windowsToClose.forEach(function(win) { + win.close(); + }); +}); + diff --git a/browser/base/content/test/newtab/browser_newtab_reflow_load.js b/browser/base/content/test/newtab/browser_newtab_reflow_load.js new file mode 100644 index 000000000..b8a24595e --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_reflow_load.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const FRAME_SCRIPT = getRootDirectory(gTestPath) + "content-reflows.js"; +const ADDITIONAL_WAIT_MS = 2000; + +/* + * Ensure that loading about:newtab doesn't cause uninterruptible reflows. + */ +add_task(function* () { + yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => { + return gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false}); + }, false); + + let browser = gBrowser.selectedBrowser; + let mm = browser.messageManager; + mm.loadFrameScript(FRAME_SCRIPT, true); + mm.addMessageListener("newtab-reflow", ({data: stack}) => { + ok(false, `unexpected uninterruptible reflow ${stack}`); + }); + + let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true); + browser.loadURI("about:newtab"); + yield browserLoadedPromise; + + // Wait some more to catch sync reflows after the page has loaded. + yield new Promise(resolve => { + setTimeout(resolve, ADDITIONAL_WAIT_MS); + }); + + // Clean up. + gBrowser.removeCurrentTab({animate: false}); + + ok(true, "Each test requires at least one pass, fail or todo so here is a pass."); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js new file mode 100644 index 000000000..24e1be369 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const PRELOAD_PREF = "browser.newtab.preload"; + +gDirectorySource = "data:application/json," + JSON.stringify({ + "directory": [{ + url: "http://example.com/organic", + type: "organic" + }, { + url: "http://localhost/sponsored", + type: "sponsored" + }] +}); + +add_task(function* () { + yield pushPrefs([PRELOAD_PREF, false]); + + let originalReportSitesAction = DirectoryLinksProvider.reportSitesAction; + registerCleanupFunction(() => { + DirectoryLinksProvider.reportSitesAction = originalReportSitesAction; + }); + + let expected = {}; + + function expectReportSitesAction() { + return new Promise(resolve => { + DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) { + let {link} = sites[siteIndex]; + is(link.type, expected.type, "got expected type"); + is(action, expected.action, "got expected action"); + is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned"); + resolve(); + } + }); + } + + // Test that the last visible site (index 1) is reported + let reportSitesPromise = expectReportSitesAction(); + expected.type = "sponsored"; + expected.action = "view"; + expected.pinned = false; + yield* addNewTabPageTab(); + yield reportSitesPromise; + + // Click the pin button on the link in the 1th tile spot + expected.action = "pin"; + // tiles become "history" when pinned + expected.type = "history"; + expected.pinned = true; + let pagesUpdatedPromise = whenPagesUpdated(); + reportSitesPromise = expectReportSitesAction(); + + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser); + yield pagesUpdatedPromise; + yield reportSitesPromise; + + // Unpin that link + expected.action = "unpin"; + expected.pinned = false; + pagesUpdatedPromise = whenPagesUpdated(); + reportSitesPromise = expectReportSitesAction(); + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser); + yield pagesUpdatedPromise; + yield reportSitesPromise; + + // Block the site in the 0th tile spot + expected.type = "organic"; + expected.action = "block"; + expected.pinned = false; + pagesUpdatedPromise = whenPagesUpdated(); + reportSitesPromise = expectReportSitesAction(); + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site .newtab-control-block", {}, gBrowser.selectedBrowser); + yield pagesUpdatedPromise; + yield reportSitesPromise; + + // Click the 1th link now in the 0th tile spot + expected.type = "history"; + expected.action = "click"; + reportSitesPromise = expectReportSitesAction(); + yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser); + yield reportSitesPromise; +}); diff --git a/browser/base/content/test/newtab/browser_newtab_search.js b/browser/base/content/test/newtab/browser_newtab_search.js new file mode 100644 index 000000000..19ed4ba74 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_search.js @@ -0,0 +1,247 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// See browser/components/search/test/browser_*_behavior.js for tests of actual +// searches. + +Cu.import("resource://gre/modules/Task.jsm"); + +const ENGINE_NO_LOGO = { + name: "searchEngineNoLogo.xml", + numLogos: 0, +}; + +const ENGINE_FAVICON = { + name: "searchEngineFavicon.xml", + logoPrefix1x: "data:image/png;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsA", + numLogos: 1, +}; +ENGINE_FAVICON.logoPrefix2x = ENGINE_FAVICON.logoPrefix1x; + +const ENGINE_1X_LOGO = { + name: "searchEngine1xLogo.xml", + logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEw", + numLogos: 1, +}; +ENGINE_1X_LOGO.logoPrefix2x = ENGINE_1X_LOGO.logoPrefix1x; + +const ENGINE_2X_LOGO = { + name: "searchEngine2xLogo.xml", + logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMU", + numLogos: 1, +}; +ENGINE_2X_LOGO.logoPrefix1x = ENGINE_2X_LOGO.logoPrefix2x; + +const ENGINE_1X_2X_LOGO = { + name: "searchEngine1x2xLogo.xml", + logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIG", + logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMo", + numLogos: 2, +}; + +const ENGINE_SUGGESTIONS = { + name: "searchSuggestionEngine.xml", + numLogos: 0, +}; + +// The test has an expected search event queue and a search event listener. +// Search events that are expected to happen are added to the queue, and the +// listener consumes the queue and ensures that each event it receives is at +// the head of the queue. +let gExpectedSearchEventQueue = []; +let gExpectedSearchEventResolver = null; + +let gNewEngines = []; + +add_task(function* () { + let oldCurrentEngine = Services.search.currentEngine; + + yield* addNewTabPageTab(); + + // The tab is removed at the end of the test, so there's no need to remove + // this listener at the end of the test. + info("Adding search event listener"); + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + const SERVICE_EVENT_NAME = "ContentSearchService"; + content.addEventListener(SERVICE_EVENT_NAME, function (event) { + sendAsyncMessage("test:search-event", { eventType: event.detail.type }); + }); + }); + + let mm = gBrowser.selectedBrowser.messageManager; + mm.addMessageListener("test:search-event", function (message) { + let eventType = message.data.eventType; + if (!gExpectedSearchEventResolver) { + ok(false, "Got search event " + eventType + " with no promise assigned"); + } + + let expectedEventType = gExpectedSearchEventQueue.shift(); + is(eventType, expectedEventType, "Got expected search event " + expectedEventType); + if (!gExpectedSearchEventQueue.length) { + gExpectedSearchEventResolver(); + gExpectedSearchEventResolver = null; + } + }); + + // Add the engine without any logos and switch to it. + let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO); + let searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = noLogoEngine; + yield searchEventsPromise; + yield* checkCurrentEngine(ENGINE_NO_LOGO); + + // Add the engine with favicon and switch to it. + let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON); + searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = faviconEngine; + yield searchEventsPromise; + yield* checkCurrentEngine(ENGINE_FAVICON); + + // Add the engine with a 1x-DPI logo and switch to it. + let logo1xEngine = yield promiseNewSearchEngine(ENGINE_1X_LOGO); + searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = logo1xEngine; + yield searchEventsPromise; + yield* checkCurrentEngine(ENGINE_1X_LOGO); + + // Add the engine with a 2x-DPI logo and switch to it. + let logo2xEngine = yield promiseNewSearchEngine(ENGINE_2X_LOGO); + searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = logo2xEngine; + yield searchEventsPromise; + yield* checkCurrentEngine(ENGINE_2X_LOGO); + + // Add the engine with 1x- and 2x-DPI logos and switch to it. + let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO); + searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = logo1x2xEngine; + yield searchEventsPromise; + yield* checkCurrentEngine(ENGINE_1X_2X_LOGO); + + // Add the engine that provides search suggestions and switch to it. + let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS); + searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = suggestionEngine; + yield searchEventsPromise; + yield* checkCurrentEngine(ENGINE_SUGGESTIONS); + + // Avoid intermittent failures. + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + content.gSearch._contentSearchController.remoteTimeout = 5000; + }); + + // Type an X in the search input. This is only a smoke test. See + // browser_searchSuggestionUI.js for comprehensive content search suggestion + // UI tests. + let suggestionsOpenPromise = new Promise(resolve => { + mm.addMessageListener("test:newtab-suggestions-open", function onResponse(message) { + mm.removeMessageListener("test:newtab-suggestions-open", onResponse); + resolve(); + }); + }); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let table = content.document.getElementById("searchSuggestionTable"); + + let input = content.document.getElementById("newtab-search-text"); + input.focus(); + + info("Waiting for suggestions table to open"); + let observer = new content.MutationObserver(() => { + if (input.getAttribute("aria-expanded") == "true") { + observer.disconnect(); + Assert.ok(!table.hidden, "Search suggestion table unhidden"); + sendAsyncMessage("test:newtab-suggestions-open", {}); + } + }); + observer.observe(input, { + attributes: true, + attributeFilter: ["aria-expanded"], + }); + }); + + let suggestionsPromise = promiseSearchEvents(["Suggestions"]); + + EventUtils.synthesizeKey("x", {}); + + // Wait for the search suggestions to become visible and for the Suggestions + // message. + yield suggestionsOpenPromise; + yield suggestionsPromise; + + // Empty the search input, causing the suggestions to be hidden. + EventUtils.synthesizeKey("a", { accelKey: true }); + EventUtils.synthesizeKey("VK_DELETE", {}); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + Assert.ok(content.document.getElementById("searchSuggestionTable").hidden, + "Search suggestion table hidden"); + }); + + // Done. Revert the current engine and remove the new engines. + searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); + Services.search.currentEngine = oldCurrentEngine; + yield searchEventsPromise; + + let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length); + searchEventsPromise = promiseSearchEvents(events); + + for (let engine of gNewEngines) { + Services.search.removeEngine(engine); + } + yield searchEventsPromise; +}); + +function promiseSearchEvents(events) { + info("Expecting search events: " + events); + return new Promise(resolve => { + gExpectedSearchEventQueue.push(...events); + gExpectedSearchEventResolver = resolve; + }); +} + +function promiseNewSearchEngine({name: basename, numLogos}) { + info("Waiting for engine to be added: " + basename); + + // Wait for the search events triggered by adding the new engine. + // engine-added engine-loaded + let expectedSearchEvents = ["CurrentState", "CurrentState"]; + // engine-changed for each of the logos + for (let i = 0; i < numLogos; i++) { + expectedSearchEvents.push("CurrentState"); + } + let eventPromise = promiseSearchEvents(expectedSearchEvents); + + // Wait for addEngine(). + let addEnginePromise = new Promise((resolve, reject) => { + let url = getRootDirectory(gTestPath) + basename; + Services.search.addEngine(url, null, "", false, { + onSuccess: function (engine) { + info("Search engine added: " + basename); + gNewEngines.push(engine); + resolve(engine); + }, + onError: function (errCode) { + ok(false, "addEngine failed with error code " + errCode); + reject(); + }, + }); + }); + + return Promise.all([addEnginePromise, eventPromise]).then(([newEngine, _]) => { + return newEngine; + }); +} + +function* checkCurrentEngine(engineInfo) +{ + let engine = Services.search.currentEngine; + ok(engine.name.includes(engineInfo.name), + "Sanity check: current engine: engine.name=" + engine.name + + " basename=" + engineInfo.name); + + yield ContentTask.spawn(gBrowser.selectedBrowser, { name: engine.name }, function* (args) { + Assert.equal(content.gSearch._contentSearchController.defaultEngine.name, + args.name, "currentEngineName: " + args.name); + }); +} diff --git a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js new file mode 100644 index 000000000..f6bb85d47 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* () { + yield setLinks("0"); + yield* addNewTabPageTab(); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + var EventUtils = {}; + EventUtils.window = {}; + EventUtils.parent = EventUtils.window; + EventUtils._EU_Ci = Components.interfaces; + + Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + let cell = content.gGrid.cells[0]; + + let site = cell.node.querySelector(".newtab-site"); + site.setAttribute("type", "sponsored"); + + // test explain text appearing upon a click + let sponsoredButton = site.querySelector(".newtab-sponsored"); + EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content); + let explain = site.querySelector(".sponsored-explain"); + Assert.notEqual(explain, null, "Sponsored explanation shown"); + Assert.ok(explain.querySelector("input").classList.contains("newtab-control-block"), + "sponsored tiles show blocked image"); + Assert.ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute"); + + // test dismissing sponsored explain + EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content); + Assert.equal(site.querySelector(".sponsored-explain"), null, + "Sponsored explanation no longer shown"); + Assert.ok(!sponsoredButton.hasAttribute("active"), + "Sponsored button does not have active attribute"); + + // test with enhanced tile + site.setAttribute("type", "enhanced"); + EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content); + explain = site.querySelector(".sponsored-explain"); + Assert.notEqual(explain, null, "Sponsored explanation shown"); + Assert.ok(explain.querySelector("input").classList.contains("newtab-customize"), + "enhanced tiles show customize image"); + Assert.ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute"); + + // test dismissing enhanced explain + EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content); + Assert.equal(site.querySelector(".sponsored-explain"), null, + "Sponsored explanation no longer shown"); + Assert.ok(!sponsoredButton.hasAttribute("active"), + "Sponsored button does not have active attribute"); + }); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_undo.js b/browser/base/content/test/newtab/browser_newtab_undo.js new file mode 100644 index 000000000..ba094cb26 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_undo.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that the undo dialog works as expected. + */ +add_task(function* () { + // remove unpinned sites and undo it + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks("5"); + + yield* addNewTabPageTab(); + yield* checkGrid("5p,0,1,2,3,4,6,7,8"); + + yield blockCell(4); + yield blockCell(4); + yield* checkGrid("5p,0,1,2,6,7,8"); + + yield* undo(); + yield* checkGrid("5p,0,1,2,4,6,7,8"); + + // now remove a pinned site and undo it + yield blockCell(0); + yield* checkGrid("0,1,2,4,6,7,8"); + + yield* undo(); + yield* checkGrid("5p,0,1,2,4,6,7,8"); + + // remove a site and restore all + yield blockCell(1); + yield* checkGrid("5p,1,2,4,6,7,8"); + + yield* undoAll(); + yield* checkGrid("5p,0,1,2,3,4,6,7,8"); +}); + +function* undo() { + let updatedPromise = whenPagesUpdated(); + yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-button", {}, gBrowser.selectedBrowser); + yield updatedPromise; +} + +function* undoAll() { + let updatedPromise = whenPagesUpdated(); + yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-restore-button", {}, gBrowser.selectedBrowser); + yield updatedPromise; +} diff --git a/browser/base/content/test/newtab/browser_newtab_unpin.js b/browser/base/content/test/newtab/browser_newtab_unpin.js new file mode 100644 index 000000000..14751465f --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_unpin.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * These tests make sure that when a site gets unpinned it is either moved to + * its actual place in the grid or removed in case it's not on the grid anymore. + */ +add_task(function* () { + // we have a pinned link that didn't change its position since it was pinned. + // nothing should happen when we unpin it. + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",1"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,1p,2,3,4,5,6,7,8"); + + yield unpinCell(1); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + // we have a pinned link that is not anymore in the list of the most-visited + // links. this should disappear, the remaining links adjust their positions + // and a new link will appear at the end of the grid. + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(",99"); + + yield* addNewTabPageTab(); + yield* checkGrid("0,99p,1,2,3,4,5,6,7"); + + yield unpinCell(1); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); + + // we have a pinned link that changed its position since it was pinned. it + // should be moved to its new position after being unpinned. + yield setLinks("0,1,2,3,4,5,6,7"); + setPinnedLinks(",1,,,,,,,0"); + + yield* addNewTabPageTab(); + yield* checkGrid("2,1p,3,4,5,6,7,,0p"); + + yield unpinCell(1); + yield* checkGrid("1,2,3,4,5,6,7,,0p"); + + yield unpinCell(8); + yield* checkGrid("0,1,2,3,4,5,6,7,"); + + // we have pinned link that changed its position since it was pinned. the + // link will disappear from the grid because it's now a much lower priority + yield setLinks("0,1,2,3,4,5,6,7,8,9"); + setPinnedLinks("9"); + + yield* addNewTabPageTab(); + yield* checkGrid("9p,0,1,2,3,4,5,6,7"); + + yield unpinCell(0); + yield* checkGrid("0,1,2,3,4,5,6,7,8"); +}); diff --git a/browser/base/content/test/newtab/browser_newtab_update.js b/browser/base/content/test/newtab/browser_newtab_update.js new file mode 100644 index 000000000..6cf089dfd --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_update.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Checks that newtab is updated as its links change. + */ +add_task(function* () { + // First, start with an empty page. setLinks will trigger a hidden page + // update because it calls clearHistory. We need to wait for that update to + // happen so that the next time we wait for a page update below, we catch the + // right update and not the one triggered by setLinks. + let updatedPromise = whenPagesUpdated(); + let setLinksPromise = setLinks([]); + yield Promise.all([updatedPromise, setLinksPromise]); + + // Strategy: Add some visits, open a new page, check the grid, repeat. + yield fillHistoryAndWaitForPageUpdate([1]); + yield* addNewTabPageTab(); + yield* checkGrid("1,,,,,,,,"); + + yield fillHistoryAndWaitForPageUpdate([2]); + yield* addNewTabPageTab(); + yield* checkGrid("2,1,,,,,,,"); + + yield fillHistoryAndWaitForPageUpdate([1]); + yield* addNewTabPageTab(); + yield* checkGrid("1,2,,,,,,,"); + + yield fillHistoryAndWaitForPageUpdate([2, 3, 4]); + yield* addNewTabPageTab(); + yield* checkGrid("2,1,3,4,,,,,"); + + // Make sure these added links have the right type + let type = yield performOnCell(1, cell => { return cell.site.link.type }); + is(type, "history", "added link is history"); +}); + +function fillHistoryAndWaitForPageUpdate(links) { + let updatedPromise = whenPagesUpdated; + let fillHistoryPromise = fillHistory(links.map(link)); + return Promise.all([updatedPromise, fillHistoryPromise]); +} + +function link(id) { + return { url: "http://example" + id + ".com/", title: "site#" + id }; +} diff --git a/browser/base/content/test/newtab/content-reflows.js b/browser/base/content/test/newtab/content-reflows.js new file mode 100644 index 000000000..f1a53782e --- /dev/null +++ b/browser/base/content/test/newtab/content-reflows.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +(function () { + "use strict"; + + const Ci = Components.interfaces; + + docShell.addWeakReflowObserver({ + reflow() { + // Gather information about the current code path. + let path = (new Error().stack).split("\n").slice(1).join("\n"); + if (path) { + sendSyncMessage("newtab-reflow", path); + } + }, + + reflowInterruptible() { + // We're not interested in interruptible reflows. + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, + Ci.nsISupportsWeakReference]) + }); +})(); diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js new file mode 100644 index 000000000..d702103a0 --- /dev/null +++ b/browser/base/content/test/newtab/head.js @@ -0,0 +1,552 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled"; +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source"; + +Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true); + +var tmp = {}; +Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp); +Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp); +Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp); +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tmp); +var {NewTabUtils, Sanitizer, DirectoryLinksProvider, PlacesTestUtils} = tmp; + +var gWindow = window; + +// Default to dummy/empty directory links +var gDirectorySource = 'data:application/json,{"test":1}'; +var gOrigDirectorySource; + +// The tests assume all 3 rows and all 3 columns of sites are shown, but the +// window may be too small to actually show everything. Resize it if necessary. +var requiredSize = {}; +requiredSize.innerHeight = + 40 + 32 + // undo container + bottom margin + 44 + 32 + // search bar + bottom margin + (3 * (180 + 32)) + // 3 rows * (tile height + title and bottom margin) + 100; // breathing room +requiredSize.innerWidth = + (3 * (290 + 20)) + // 3 cols * (tile width + side margins) + 100; // breathing room + +var oldSize = {}; +Object.keys(requiredSize).forEach(prop => { + info([prop, gBrowser.contentWindow[prop], requiredSize[prop]]); + if (gBrowser.contentWindow[prop] < requiredSize[prop]) { + oldSize[prop] = gBrowser.contentWindow[prop]; + info("Changing browser " + prop + " from " + oldSize[prop] + " to " + + requiredSize[prop]); + gBrowser.contentWindow[prop] = requiredSize[prop]; + } +}); + +var screenHeight = {}; +var screenWidth = {}; +Cc["@mozilla.org/gfx/screenmanager;1"]. + getService(Ci.nsIScreenManager). + primaryScreen. + GetAvailRectDisplayPix({}, {}, screenWidth, screenHeight); +screenHeight = screenHeight.value; +screenWidth = screenWidth.value; + +if (screenHeight < gBrowser.contentWindow.outerHeight) { + info("Warning: Browser outer height is now " + + gBrowser.contentWindow.outerHeight + ", which is larger than the " + + "available screen height, " + screenHeight + + ". That may cause problems."); +} + +if (screenWidth < gBrowser.contentWindow.outerWidth) { + info("Warning: Browser outer width is now " + + gBrowser.contentWindow.outerWidth + ", which is larger than the " + + "available screen width, " + screenWidth + + ". That may cause problems."); +} + +registerCleanupFunction(function () { + while (gWindow.gBrowser.tabs.length > 1) + gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]); + + Object.keys(oldSize).forEach(prop => { + if (oldSize[prop]) { + gBrowser.contentWindow[prop] = oldSize[prop]; + } + }); + + // Stop any update timers to prevent unexpected updates in later tests + let timer = NewTabUtils.allPages._scheduleUpdateTimeout; + if (timer) { + clearTimeout(timer); + delete NewTabUtils.allPages._scheduleUpdateTimeout; + } + + Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED); + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource); + + return watchLinksChangeOnce(); +}); + +function pushPrefs(...aPrefs) { + return new Promise(resolve => + SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve)); +} + +/** + * Resolves promise when directory links are downloaded and written to disk + */ +function watchLinksChangeOnce() { + return new Promise(resolve => { + let observer = { + onManyLinksChanged: () => { + DirectoryLinksProvider.removeObserver(observer); + resolve(); + } + }; + observer.onDownloadFail = observer.onManyLinksChanged; + DirectoryLinksProvider.addObserver(observer); + }); +} + +add_task(function* setup() { + registerCleanupFunction(function() { + return new Promise(resolve => { + function cleanupAndFinish() { + PlacesTestUtils.clearHistory().then(() => { + whenPagesUpdated().then(resolve); + NewTabUtils.restore(); + }); + } + + let callbacks = NewTabUtils.links._populateCallbacks; + let numCallbacks = callbacks.length; + + if (numCallbacks) + callbacks.splice(0, numCallbacks, cleanupAndFinish); + else + cleanupAndFinish(); + }); + }); + + let promiseReady = Task.spawn(function*() { + yield watchLinksChangeOnce(); + yield whenPagesUpdated(); + }); + + // Save the original directory source (which is set globally for tests) + gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE); + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource); + yield promiseReady; +}); + +/** Perform an action on a cell within the newtab page. + * @param aIndex index of cell + * @param aFn function to call in child process or tab. + * @returns result of calling the function. + */ +function performOnCell(aIndex, aFn) { + return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, + { index: aIndex, fn: aFn.toString() }, function* (args) { + let cell = content.gGrid.cells[args.index]; + return eval(args.fn)(cell); + }); +} + +/** + * Allows to provide a list of links that is used to construct the grid. + * @param aLinksPattern the pattern (see below) + * + * Example: setLinks("-1,0,1,2,3") + * Result: [{url: "http://example.com/", title: "site#-1"}, + * {url: "http://example0.com/", title: "site#0"}, + * {url: "http://example1.com/", title: "site#1"}, + * {url: "http://example2.com/", title: "site#2"}, + * {url: "http://example3.com/", title: "site#3"}] + */ +function setLinks(aLinks) { + return new Promise(resolve => { + let links = aLinks; + + if (typeof links == "string") { + links = aLinks.split(/\s*,\s*/).map(function (id) { + return {url: "http://example" + (id != "-1" ? id : "") + ".com/", + title: "site#" + id}; + }); + } + + // Call populateCache() once to make sure that all link fetching that is + // currently in progress has ended. We clear the history, fill it with the + // given entries and call populateCache() now again to make sure the cache + // has the desired contents. + NewTabUtils.links.populateCache(function () { + PlacesTestUtils.clearHistory().then(() => { + fillHistory(links).then(() => { + NewTabUtils.links.populateCache(function () { + NewTabUtils.allPages.update(); + resolve(); + }, true); + }); + }); + }); + }); +} + +function fillHistory(aLinks) { + return new Promise(resolve => { + let numLinks = aLinks.length; + if (!numLinks) { + executeSoon(resolve); + return; + } + + let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK; + + // Important: To avoid test failures due to clock jitter on Windows XP, call + // Date.now() once here, not each time through the loop. + let now = Date.now() * 1000; + + for (let i = 0; i < aLinks.length; i++) { + let link = aLinks[i]; + let place = { + uri: makeURI(link.url), + title: link.title, + // Links are secondarily sorted by visit date descending, so decrease the + // visit date as we progress through the array so that links appear in the + // grid in the order they're present in the array. + visits: [{visitDate: now - i, transitionType: transitionLink}] + }; + + PlacesUtils.asyncHistory.updatePlaces(place, { + handleError: () => ok(false, "couldn't add visit to history"), + handleResult: function () {}, + handleCompletion: function () { + if (--numLinks == 0) { + resolve(); + } + } + }); + } + }); +} + +/** + * Allows to specify the list of pinned links (that have a fixed position in + * the grid. + * @param aLinksPattern the pattern (see below) + * + * Example: setPinnedLinks("3,,1") + * Result: 'http://example3.com/' is pinned in the first cell. 'http://example1.com/' is + * pinned in the third cell. + */ +function setPinnedLinks(aLinks) { + let links = aLinks; + + if (typeof links == "string") { + links = aLinks.split(/\s*,\s*/).map(function (id) { + if (id) + return {url: "http://example" + (id != "-1" ? id : "") + ".com/", + title: "site#" + id, + type: "history"}; + return undefined; + }); + } + + let string = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + string.data = JSON.stringify(links); + Services.prefs.setComplexValue("browser.newtabpage.pinned", + Ci.nsISupportsString, string); + + NewTabUtils.pinnedLinks.resetCache(); + NewTabUtils.allPages.update(); +} + +/** + * Restore the grid state. + */ +function restore() { + return new Promise(resolve => { + whenPagesUpdated().then(resolve); + NewTabUtils.restore(); + }); +} + +/** + * Wait until a given condition becomes true. + */ +function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) { + return new Promise((resolve, reject) => { + let tries = 0; + + function tryNow() { + tries++; + + if (aConditionFn()) { + resolve(); + } else if (tries < aMaxTries) { + tryAgain(); + } else { + reject("Condition timed out: " + aConditionFn.toSource()); + } + } + + function tryAgain() { + setTimeout(tryNow, aCheckInterval); + } + + tryAgain(); + }); +} + +/** + * Creates a new tab containing 'about:newtab'. + */ +function* addNewTabPageTab() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gWindow.gBrowser, "about:newtab", false); + let browser = tab.linkedBrowser; + + // Wait for the document to become visible in case it was preloaded. + yield waitForCondition(() => !browser.contentDocument.hidden) + + yield new Promise(resolve => { + if (NewTabUtils.allPages.enabled) { + // Continue when the link cache has been populated. + NewTabUtils.links.populateCache(function () { + whenSearchInitDone().then(resolve); + }); + } else { + resolve(); + } + }); + + return tab; +} + +/** + * Compares the current grid arrangement with the given pattern. + * @param the pattern (see below) + * + * Example: checkGrid("3p,2,,1p") + * Result: We expect the first cell to contain the pinned site 'http://example3.com/'. + * The second cell contains 'http://example2.com/'. The third cell is empty. + * The fourth cell contains the pinned site 'http://example4.com/'. + */ +function* checkGrid(pattern) { + let length = pattern.split(",").length; + + yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, + { length, pattern }, function* (args) { + let grid = content.wrappedJSObject.gGrid; + + let sites = grid.sites.slice(0, args.length); + let foundPattern = sites.map(function (aSite) { + if (!aSite) + return ""; + + let pinned = aSite.isPinned(); + let hasPinnedAttr = aSite.node.hasAttribute("pinned"); + + if (pinned != hasPinnedAttr) + ok(false, "invalid state (site.isPinned() != site[pinned])"); + + return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : ""); + }); + + Assert.equal(foundPattern, args.pattern, "grid status = " + args.pattern); + }); +} + +/** + * Blocks a site from the grid. + * @param aIndex The cell index. + */ +function blockCell(aIndex) { + return new Promise(resolve => { + whenPagesUpdated().then(resolve); + performOnCell(aIndex, cell => { + return cell.site.block(); + }); + }); +} + +/** + * Pins a site on a given position. + * @param aIndex The cell index. + * @param aPinIndex The index the defines where the site should be pinned. + */ +function pinCell(aIndex) { + performOnCell(aIndex, cell => { + cell.site.pin(); + }); +} + +/** + * Unpins the given cell's site. + * @param aIndex The cell index. + */ +function unpinCell(aIndex) { + return new Promise(resolve => { + whenPagesUpdated().then(resolve); + performOnCell(aIndex, cell => { + cell.site.unpin(); + }); + }); +} + +/** + * Simulates a drag and drop operation. Instead of rearranging a site that is + * is already contained in the newtab grid, this is used to simulate dragging + * an external link onto the grid e.g. the text from the URL bar. + * @param aDestIndex The cell index of the drop target. + */ +function* simulateExternalDrop(aDestIndex) { + let pagesUpdatedPromise = whenPagesUpdated(); + + yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aDestIndex, function*(dropIndex) { + return new Promise(resolve => { + const url = "data:text/html;charset=utf-8," + + "<a id='link' href='http://example99.com/'>link</a>"; + + let doc = content.document; + let iframe = doc.createElement("iframe"); + + function iframeLoaded() { + let dataTransfer = new iframe.contentWindow.DataTransfer("dragstart", false); + dataTransfer.mozSetDataAt("text/x-moz-url", "http://example99.com/", 0); + + let event = content.document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + + let target = content.gGrid.cells[dropIndex].node; + target.dispatchEvent(event); + + iframe.remove(); + + resolve(); + } + + iframe.addEventListener("load", function onLoad() { + iframe.removeEventListener("load", onLoad); + content.setTimeout(iframeLoaded, 0); + }); + + iframe.setAttribute("src", url); + iframe.style.width = "50px"; + iframe.style.height = "50px"; + iframe.style.position = "absolute"; + iframe.style.zIndex = 50; + + // the frame has to be attached to a visible element + let margin = doc.getElementById("newtab-search-container"); + margin.appendChild(iframe); + }); + }); + + yield pagesUpdatedPromise; +} + +/** + * Resumes testing when all pages have been updated. + */ +function whenPagesUpdated() { + return new Promise(resolve => { + let page = { + observe: _ => _, + + update() { + NewTabUtils.allPages.unregister(this); + executeSoon(resolve); + } + }; + + NewTabUtils.allPages.register(page); + registerCleanupFunction(function () { + NewTabUtils.allPages.unregister(page); + }); + }); +} + +/** + * Waits for the response to the page's initial search state request. + */ +function whenSearchInitDone() { + return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() { + return new Promise(resolve => { + if (content.gSearch) { + let searchController = content.gSearch._contentSearchController; + if (searchController.defaultEngine) { + resolve(); + return; + } + } + + let eventName = "ContentSearchService"; + content.addEventListener(eventName, function onEvent(event) { + if (event.detail.type == "State") { + content.removeEventListener(eventName, onEvent); + let resolver = function() { + // Wait for the search controller to receive the event, then resolve. + if (content.gSearch._contentSearchController.defaultEngine) { + resolve(); + return; + } + } + content.setTimeout(resolver, 0); + } + }); + }); + }); +} + +/** + * Changes the newtab customization option and waits for the panel to open and close + * + * @param {string} aTheme + * Can be any of("blank"|"classic"|"enhanced") + */ +function customizeNewTabPage(aTheme) { + return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aTheme, function*(aTheme) { + + let document = content.document; + let panel = document.getElementById("newtab-customize-panel"); + let customizeButton = document.getElementById("newtab-customize-button"); + + function panelOpened(opened) { + return new Promise( (resolve) => { + let options = {attributes: true, oldValue: true}; + let observer = new content.MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + document.getElementById("newtab-customize-" + aTheme).click(); + observer.disconnect(); + if (opened == panel.hasAttribute("open")) { + resolve(); + } + }); + }); + observer.observe(panel, options); + }); + } + + let opened = panelOpened(true); + customizeButton.click(); + yield opened; + + let closed = panelOpened(false); + customizeButton.click(); + yield closed; + }); +} + +/** + * Reports presence of a scrollbar + */ +function hasScrollbar() { + return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function* () { + let docElement = content.document.documentElement; + return docElement.scrollHeight > docElement.clientHeight; + }); +} diff --git a/browser/base/content/test/newtab/searchEngine1x2xLogo.xml b/browser/base/content/test/newtab/searchEngine1x2xLogo.xml new file mode 100644 index 000000000..c8b6749b3 --- /dev/null +++ b/browser/base/content/test/newtab/searchEngine1x2xLogo.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_newtab_search searchEngine1x2xLogo.xml</ShortName> +<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1x2xlogo" rel="searchform"/> +<!-- #00FF00 --> +<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIGr0+8uwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVRYw+3PAREAAAQEsKd/Z3I4W4NVJtd14uDg4ODg4ODg4ODg4ODg4ODg8Pqw7M0BM+n9I0oAAAAASUVORK5CYII=</Image> +<!-- #00FFFF --> +<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMoaoKANQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAYklEQVR42u3RQQ0AAAjEMMC/58MECZ9OwtqVlL4bCzAIAwZhwCAMGIQBgzBgEAYMwoBBGDAIAwZhwCAMGIQBgzBgEAYMwoBBGIQBgzBgEAYMwoBBGDAIAwZhwCAMGIQBg+5ar7sCZiri9VUAAAAASUVORK5CYII=</Image> +</SearchPlugin> diff --git a/browser/base/content/test/newtab/searchEngine1xLogo.xml b/browser/base/content/test/newtab/searchEngine1xLogo.xml new file mode 100644 index 000000000..19ac03f48 --- /dev/null +++ b/browser/base/content/test/newtab/searchEngine1xLogo.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_newtab_search searchEngine1xLogo.xml</ShortName> +<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1xlogo" rel="searchform"/> +<!-- #FF0000 --> +<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEwS9h64QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVRYw+3PAREAAAQEsKd/Z3I4W4PV5LyOg4ODg4ODg4ODg4ODg4ODg4PD78MC7cwBM02qquMAAAAASUVORK5CYII=</Image> +</SearchPlugin> diff --git a/browser/base/content/test/newtab/searchEngine2xLogo.xml b/browser/base/content/test/newtab/searchEngine2xLogo.xml new file mode 100644 index 000000000..941bf040d --- /dev/null +++ b/browser/base/content/test/newtab/searchEngine2xLogo.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_newtab_search searchEngine2xLogo.xml</ShortName> +<Url type="text/html" method="GET" template="http://browser-newtab-search.com/2xlogo" rel="searchform"/> +<!-- #0000FF --> +<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMURe38sgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAYklEQVR42u3RQQ0AAAjEsAP/nsEECZ9OwlrJRN+1BRiEAYMwYBAGDMKAQRgwCAMGYcAgDBiEAYMwYBAGDMKAQRgwCAMGYcAgDMKAQRgwCAMGYcAgDBiEAYMwYBAGDMKAQXctkIQBZ/1YP3YAAAAASUVORK5CYII=</Image> +</SearchPlugin> diff --git a/browser/base/content/test/newtab/searchEngineFavicon.xml b/browser/base/content/test/newtab/searchEngineFavicon.xml new file mode 100644 index 000000000..6f2a970f5 --- /dev/null +++ b/browser/base/content/test/newtab/searchEngineFavicon.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_newtab_search searchEngineFavicon.xml</ShortName> +<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1xlogo" rel="searchform"/> +<Image width="16" height="16">data:application/ico;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAA/wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAAAAAABAAAEwsAABMLAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image> +</SearchPlugin> diff --git a/browser/base/content/test/newtab/searchEngineNoLogo.xml b/browser/base/content/test/newtab/searchEngineNoLogo.xml new file mode 100644 index 000000000..bbff6cf8f --- /dev/null +++ b/browser/base/content/test/newtab/searchEngineNoLogo.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_newtab_search searchEngineNoLogo.xml</ShortName> +<Url type="text/html" method="GET" template="http://browser-newtab-search.com/nologo" rel="searchform"/> +</SearchPlugin> diff --git a/browser/base/content/test/plugins/.eslintrc.js b/browser/base/content/test/plugins/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/plugins/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/plugins/blockNoPlugins.xml b/browser/base/content/test/plugins/blockNoPlugins.xml new file mode 100644 index 000000000..e4e191b37 --- /dev/null +++ b/browser/base/content/test/plugins/blockNoPlugins.xml @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310001"> + <emItems> + </emItems> + <pluginItems> + </pluginItems> +</blocklist> diff --git a/browser/base/content/test/plugins/blockPluginHard.xml b/browser/base/content/test/plugins/blockPluginHard.xml new file mode 100644 index 000000000..24eb5bc6f --- /dev/null +++ b/browser/base/content/test/plugins/blockPluginHard.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000"> + <emItems> + </emItems> + <pluginItems> + <pluginItem blockID="p9999"> + <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" /> + <versionRange severity="2"></versionRange> + </pluginItem> + </pluginItems> +</blocklist> diff --git a/browser/base/content/test/plugins/blockPluginInfoURL.xml b/browser/base/content/test/plugins/blockPluginInfoURL.xml new file mode 100644 index 000000000..c16808896 --- /dev/null +++ b/browser/base/content/test/plugins/blockPluginInfoURL.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000"> + <emItems> + </emItems> + <pluginItems> + <pluginItem blockID="p9999"> + <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" /> + <versionRange severity="2"></versionRange> + <infoURL>http://test.url.com/</infoURL> + </pluginItem> + </pluginItems> +</blocklist> diff --git a/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml b/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml new file mode 100644 index 000000000..bf8545afe --- /dev/null +++ b/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000"> + <emItems> + </emItems> + <pluginItems> + <pluginItem blockID="p9999"> + <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" /> + <versionRange severity="0" vulnerabilitystatus="2"></versionRange> + </pluginItem> + </pluginItems> +</blocklist> diff --git a/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml b/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml new file mode 100644 index 000000000..5545162b1 --- /dev/null +++ b/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000"> + <emItems> + </emItems> + <pluginItems> + <pluginItem blockID="p9999"> + <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" /> + <versionRange severity="0" vulnerabilitystatus="1"></versionRange> + </pluginItem> + </pluginItems> +</blocklist> diff --git a/browser/base/content/test/plugins/blocklist_proxy.js b/browser/base/content/test/plugins/blocklist_proxy.js new file mode 100644 index 000000000..1a4ed4726 --- /dev/null +++ b/browser/base/content/test/plugins/blocklist_proxy.js @@ -0,0 +1,78 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cm = Components.manager; + +const kBlocklistServiceUUID = "{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"; +const kBlocklistServiceContractID = "@mozilla.org/extensions/blocklist;1"; +const kBlocklistServiceFactory = Cm.getClassObject(Cc[kBlocklistServiceContractID], Ci.nsIFactory); + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +/* + * A lightweight blocklist proxy for the testing purposes. + */ +var BlocklistProxy = { + _uuid: null, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsIBlocklistService, + Ci.nsITimerCallback]), + + init: function() { + if (!this._uuid) { + this._uuid = + Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator) + .generateUUID(); + Cm.nsIComponentRegistrar.registerFactory(this._uuid, "", + "@mozilla.org/extensions/blocklist;1", + this); + } + }, + + uninit: function() { + if (this._uuid) { + Cm.nsIComponentRegistrar.unregisterFactory(this._uuid, this); + Cm.nsIComponentRegistrar.registerFactory(Components.ID(kBlocklistServiceUUID), + "Blocklist Service", + "@mozilla.org/extensions/blocklist;1", + kBlocklistServiceFactory); + this._uuid = null; + } + }, + + notify: function (aTimer) { + }, + + observe: function (aSubject, aTopic, aData) { + }, + + isAddonBlocklisted: function (aAddon, aAppVersion, aToolkitVersion) { + return false; + }, + + getAddonBlocklistState: function (aAddon, aAppVersion, aToolkitVersion) { + return 0; // STATE_NOT_BLOCKED + }, + + getPluginBlocklistState: function (aPluginTag, aAppVersion, aToolkitVersion) { + return 0; // STATE_NOT_BLOCKED + }, + + getAddonBlocklistURL: function (aAddon, aAppVersion, aToolkitVersion) { + return ""; + }, + + getPluginBlocklistURL: function (aPluginTag) { + return ""; + }, + + getPluginInfoURL: function (aPluginTag) { + return ""; + }, +} + +BlocklistProxy.init(); +addEventListener("unload", () => { + BlocklistProxy.uninit(); +}); diff --git a/browser/base/content/test/plugins/browser.ini b/browser/base/content/test/plugins/browser.ini new file mode 100644 index 000000000..cfc1f769c --- /dev/null +++ b/browser/base/content/test/plugins/browser.ini @@ -0,0 +1,78 @@ +[DEFAULT] +support-files = + blocklist_proxy.js + blockNoPlugins.xml + blockPluginHard.xml + blockPluginInfoURL.xml + blockPluginVulnerableNoUpdate.xml + blockPluginVulnerableUpdatable.xml + browser_clearplugindata.html + browser_clearplugindata_noage.html + head.js + plugin_add_dynamically.html + plugin_alternate_content.html + plugin_big.html + plugin_both.html + plugin_both2.html + plugin_bug744745.html + plugin_bug749455.html + plugin_bug787619.html + plugin_bug797677.html + plugin_bug820497.html + plugin_clickToPlayAllow.html + plugin_clickToPlayDeny.html + plugin_data_url.html + plugin_hidden_to_visible.html + plugin_iframe.html + plugin_outsideScrollArea.html + plugin_overlayed.html + plugin_positioned.html + plugin_small.html + plugin_small_2.html + plugin_syncRemoved.html + plugin_test.html + plugin_test2.html + plugin_test3.html + plugin_two_types.html + plugin_unknown.html + plugin_crashCommentAndURL.html + plugin_zoom.html + +[browser_bug743421.js] +[browser_bug744745.js] +[browser_bug787619.js] +[browser_bug797677.js] +[browser_bug812562.js] +[browser_bug818118.js] +[browser_bug820497.js] +[browser_clearplugindata.js] +[browser_CTP_context_menu.js] +skip-if = toolkit == "gtk2" || toolkit == "gtk3" # fails intermittently on Linux (bug 909342) +[browser_CTP_crashreporting.js] +skip-if = !crashreporter +[browser_CTP_data_urls.js] +[browser_CTP_drag_drop.js] +[browser_CTP_hide_overlay.js] +[browser_CTP_iframe.js] +[browser_CTP_multi_allow.js] +[browser_CTP_nonplugins.js] +[browser_CTP_notificationBar.js] +[browser_CTP_outsideScrollArea.js] +[browser_CTP_remove_navigate.js] +[browser_CTP_resize.js] +[browser_CTP_zoom.js] +[browser_blocking.js] +[browser_plugins_added_dynamically.js] +[browser_pluginnotification.js] +[browser_plugin_reloading.js] +[browser_blocklist_content.js] +skip-if = !e10s +[browser_globalplugin_crashinfobar.js] +skip-if = !crashreporter +[browser_pluginCrashCommentAndURL.js] +skip-if = !crashreporter +[browser_pageInfo_plugins.js] +[browser_pluginCrashReportNonDeterminism.js] +skip-if = !crashreporter || os == 'linux' # Bug 1152811 +[browser_private_clicktoplay.js] + diff --git a/browser/base/content/test/plugins/browser_CTP_context_menu.js b/browser/base/content/test/plugins/browser_CTP_context_menu.js new file mode 100644 index 000000000..03f3e18ef --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_context_menu.js @@ -0,0 +1,69 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +// Test that the activate action in content menus for CTP plugins works +add_task(function* () { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + let bindingPromise = waitForEvent(gBrowser.selectedBrowser, "PluginBindingAttached", null, true, true); + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + yield bindingPromise; + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(popupNotification, "Test 1, Should have a click-to-play notification"); + + // check plugin state + let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser); + ok(!pluginInfo.activated, "plugin should not be activated"); + + // Display a context menu on the test plugin so we can test + // activation menu options. + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("contextmenu", left, top, 2, 1, 0); + }); + + popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(popupNotification, "Should have a click-to-play notification"); + ok(popupNotification.dismissed, "notification should be dismissed"); + + // fixes a occasional test timeout on win7 opt + yield promiseForCondition(() => document.getElementById("context-ctp-play")); + + let actMenuItem = document.getElementById("context-ctp-play"); + ok(actMenuItem, "Should have a context menu entry for activating the plugin"); + + // Activate the plugin via the context menu + EventUtils.synthesizeMouseAtCenter(actMenuItem, {}); + + yield promiseForCondition(() => !PopupNotifications.panel.dismissed && PopupNotifications.panel.firstChild); + + // Activate the plugin + PopupNotifications.panel.firstChild._primaryButton.click(); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser); + ok(pluginInfo.activated, "plugin should not be activated"); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_crashreporting.js b/browser/base/content/test/plugins/browser_CTP_crashreporting.js new file mode 100644 index 000000000..bb52d5704 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_crashreporting.js @@ -0,0 +1,233 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; +const PLUGIN_PAGE = gTestRoot + "plugin_big.html"; +const PLUGIN_SMALL_PAGE = gTestRoot + "plugin_small.html"; + +/** + * Takes an nsIPropertyBag and converts it into a JavaScript Object. It + * will also convert any nsIPropertyBag's within the nsIPropertyBag + * recursively. + * + * @param aBag + * The nsIPropertyBag to convert. + * @return Object + * Keyed on the names of the nsIProperty's within the nsIPropertyBag, + * and mapping to their values. + */ +function convertPropertyBag(aBag) { + let result = {}; + let enumerator = aBag.enumerator; + while (enumerator.hasMoreElements()) { + let { name, value } = enumerator.getNext().QueryInterface(Ci.nsIProperty); + if (value instanceof Ci.nsIPropertyBag) { + value = convertPropertyBag(value); + } + result[name] = value; + } + return result; +} + +add_task(function* setup() { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin + // crash reports. This test needs them enabled. The test also needs a mock + // report server, and fortunately one is already set up by toolkit/ + // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL, + // which CrashSubmit.jsm uses as a server override. + let env = Cc["@mozilla.org/process/environment;1"]. + getService(Components.interfaces.nsIEnvironment); + let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT"); + let serverURL = env.get("MOZ_CRASHREPORTER_URL"); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + env.set("MOZ_CRASHREPORTER_URL", SERVER_URL); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + registerCleanupFunction(function cleanUp() { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); + env.set("MOZ_CRASHREPORTER_URL", serverURL); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + window.focus(); + }); +}); + +/** + * Test that plugin crash submissions still work properly after + * click-to-play activation. + */ +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PLUGIN_PAGE, + }, function* (browser) { + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(browser); + + let pluginInfo = yield promiseForPluginInfo("test", browser); + ok(!pluginInfo.activated, "Plugin should not be activated"); + + // Simulate clicking the "Allow Always" button. + let notification = PopupNotifications.getNotification("click-to-play-plugins", browser); + yield promiseForNotificationShown(notification, browser); + PopupNotifications.panel.firstChild._primaryButton.click(); + + // Prepare a crash report topic observer that only returns when + // the crash report has been successfully sent. + let crashReportChecker = (subject, data) => { + return (data == "success"); + }; + let crashReportPromise = TestUtils.topicObserved("crash-report-status", + crashReportChecker); + + yield ContentTask.spawn(browser, null, function*() { + let plugin = content.document.getElementById("test"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + + yield ContentTaskUtils.waitForCondition(() => { + return plugin.activated; + }, "Waited too long for plugin to activate."); + + try { + Components.utils.waiveXrays(plugin).crash(); + } catch (e) { + } + + let doc = plugin.ownerDocument; + + let getUI = (anonid) => { + return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid); + }; + + // Now wait until the plugin crash report UI shows itself, which is + // asynchronous. + let statusDiv; + + yield ContentTaskUtils.waitForCondition(() => { + statusDiv = getUI("submitStatus"); + return statusDiv.getAttribute("status") == "please"; + }, "Waited too long for plugin to show crash report UI"); + + // Make sure the UI matches our expectations... + let style = content.getComputedStyle(getUI("pleaseSubmit")); + if (style.display != "block") { + throw new Error(`Submission UI visibility is not correct. ` + + `Expected block style, got ${style.display}.`); + } + + // Fill the crash report in with some test values that we'll test for in + // the parent. + getUI("submitComment").value = "a test comment"; + let optIn = getUI("submitURLOptIn"); + if (!optIn.checked) { + throw new Error("URL opt-in should default to true."); + } + + // Submit the report. + optIn.click(); + getUI("submitButton").click(); + + // And wait for the parent to say that the crash report was submitted + // successfully. + yield ContentTaskUtils.waitForCondition(() => { + return statusDiv.getAttribute("status") == "success"; + }, "Timed out waiting for plugin binding to be in success state"); + }); + + let [subject, ] = yield crashReportPromise; + + ok(subject instanceof Ci.nsIPropertyBag, + "The crash report subject should be an nsIPropertyBag."); + + let crashData = convertPropertyBag(subject); + ok(crashData.serverCrashID, "Should have a serverCrashID set."); + + // Remove the submitted report file after ensuring it exists. + let file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + file.initWithPath(Services.crashmanager._submittedDumpsDir); + file.append(crashData.serverCrashID + ".txt"); + ok(file.exists(), "Submitted report file should exist"); + file.remove(false); + + ok(crashData.extra, "Extra data should exist"); + is(crashData.extra.PluginUserComment, "a test comment", + "Comment in extra data should match comment in textbox"); + + is(crashData.extra.PluginContentURL, undefined, + "URL should be absent from extra data when opt-in not checked"); + }); +}); + +/** + * Test that plugin crash submissions still work properly after + * click-to-play with the notification bar. + */ +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PLUGIN_SMALL_PAGE, + }, function* (browser) { + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(browser); + + let pluginInfo = yield promiseForPluginInfo("test", browser); + ok(pluginInfo.activated, "Plugin should be activated from previous test"); + + // Prepare a crash report topic observer that only returns when + // the crash report has been successfully sent. + let crashReportChecker = (subject, data) => { + return (data == "success"); + }; + let crashReportPromise = TestUtils.topicObserved("crash-report-status", + crashReportChecker); + + yield ContentTask.spawn(browser, null, function*() { + let plugin = content.document.getElementById("test"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + + yield ContentTaskUtils.waitForCondition(() => { + return plugin.activated; + }, "Waited too long for plugin to activate."); + + try { + Components.utils.waiveXrays(plugin).crash(); + } catch (e) {} + }); + + // Wait for the notification bar to be displayed. + let notification = yield waitForNotificationBar("plugin-crashed", browser); + + // Then click the button to submit the crash report. + let buttons = notification.querySelectorAll(".notification-button"); + is(buttons.length, 2, "Should have two buttons."); + + // The "Submit Crash Report" button should be the second one. + let submitButton = buttons[1]; + submitButton.click(); + + let [subject, ] = yield crashReportPromise; + + ok(subject instanceof Ci.nsIPropertyBag, + "The crash report subject should be an nsIPropertyBag."); + + let crashData = convertPropertyBag(subject); + ok(crashData.serverCrashID, "Should have a serverCrashID set."); + + // Remove the submitted report file after ensuring it exists. + let file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + file.initWithPath(Services.crashmanager._submittedDumpsDir); + file.append(crashData.serverCrashID + ".txt"); + ok(file.exists(), "Submitted report file should exist"); + file.remove(false); + + is(crashData.extra.PluginContentURL, undefined, + "URL should be absent from extra data when opt-in not checked"); + }); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_data_urls.js b/browser/base/content/test/plugins/browser_CTP_data_urls.js new file mode 100644 index 000000000..0f4747b1e --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_data_urls.js @@ -0,0 +1,255 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); +}); + +// Test that the click-to-play doorhanger still works when navigating to data URLs +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 1a, Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 1a, plugin should not be activated"); + + let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + // navigate forward to a page with 'test' in it + content.document.getElementById("data-link-1").click(); + }); + yield loadPromise; + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 1b, Should have a click-to-play notification"); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 1b, plugin should not be activated"); + + let promise = promisePopupNotification("click-to-play-plugins"); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + yield promise; + + // Simulate clicking the "Allow Always" button. + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed && + PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + PopupNotifications.panel.firstChild._primaryButton.click(); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 1b, plugin should be activated"); +}); + +// Test that the click-to-play notification doesn't break when navigating +// to data URLs with multiple plugins. +add_task(function* () { + // We click activated above + clearAllPluginPermissions(); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 2a, Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 2a, plugin should not be activated"); + + let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + // navigate forward to a page with 'test1' & 'test2' in it + content.document.getElementById("data-link-2").click(); + }); + yield loadPromise; + + // Work around for delayed PluginBindingAttached + yield ContentTask.spawn(gTestBrowser, {}, function* () { + content.document.getElementById("test1").clientTop; + content.document.getElementById("test2").clientTop; + }); + + pluginInfo = yield promiseForPluginInfo("test1"); + ok(!pluginInfo.activated, "Test 2a, test1 should not be activated"); + pluginInfo = yield promiseForPluginInfo("test2"); + ok(!pluginInfo.activated, "Test 2a, test2 should not be activated"); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 2b, Should have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + // Simulate choosing "Allow now" for the test plugin + is(notification.options.pluginData.size, 2, "Test 2b, Should have two types of plugin in the notification"); + + let centerAction = null; + for (let action of notification.options.pluginData.values()) { + if (action.pluginName == "Test") { + centerAction = action; + break; + } + } + ok(centerAction, "Test 2b, found center action for the Test plugin"); + + let centerItem = null; + for (let item of PopupNotifications.panel.firstChild.childNodes) { + is(item.value, "block", "Test 2b, all plugins should start out blocked"); + if (item.action == centerAction) { + centerItem = item; + break; + } + } + ok(centerItem, "Test 2b, found center item for the Test plugin"); + + // "click" the button to activate the Test plugin + centerItem.value = "allownow"; + PopupNotifications.panel.firstChild._primaryButton.click(); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("test1"); + ok(pluginInfo.activated, "Test 2b, plugin should be activated"); +}); + +add_task(function* () { + // We click activated above + clearAllPluginPermissions(); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); +}); + +// Test that when navigating to a data url, the plugin permission is inherited +add_task(function* () { + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 3a, Should have a click-to-play notification"); + + // check plugin state + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 3a, plugin should not be activated"); + + let promise = promisePopupNotification("click-to-play-plugins"); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + yield promise; + + // Simulate clicking the "Allow Always" button. + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed && + PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + PopupNotifications.panel.firstChild._primaryButton.click(); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 3a, plugin should be activated"); + + let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + // navigate forward to a page with 'test' in it + content.document.getElementById("data-link-1").click(); + }); + yield loadPromise; + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 3b, plugin should be activated"); + + clearAllPluginPermissions(); +}); + +// Test that the click-to-play doorhanger still works +// when directly navigating to data URLs. +// Fails, bug XXX. Plugins plus a data url don't fire a load event. +/* +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, + "data:text/html,Hi!<embed id='test' style='width:200px; height:200px' type='application/x-test'/>"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 4a, Should have a click-to-play notification"); + + // check plugin state + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 4a, plugin should not be activated"); + + let promise = promisePopupNotification("click-to-play-plugins"); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + yield promise; + + // Simulate clicking the "Allow Always" button. + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed && + PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + PopupNotifications.panel.firstChild._primaryButton.click(); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 4a, plugin should be activated"); +}); +*/ diff --git a/browser/base/content/test/plugins/browser_CTP_drag_drop.js b/browser/base/content/test/plugins/browser_CTP_drag_drop.js new file mode 100644 index 000000000..7c9858e27 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_drag_drop.js @@ -0,0 +1,96 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gNewWindow = null; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gNewWindow.close(); + gNewWindow = null; + window.focus(); + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + + yield promisePopupNotification("click-to-play-plugins"); +}); + +add_task(function* () { + gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + + // XXX technically can't load fire before we get this call??? + yield waitForEvent(gNewWindow, "load", null, true); + + yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser); + + ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window"); + ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now"); +}); + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, gNewWindow.gBrowser.selectedTab); + + yield promisePopupNotification("click-to-play-plugins", gBrowser.selectedBrowser); + + ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab again"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); +}); + +add_task(function* () { + yield promisePopupNotification("click-to-play-plugins"); + + gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + + yield promiseWaitForFocus(gNewWindow); + + yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser); +}); + +add_task(function* () { + ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window"); + ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now"); + + let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser); + ok(!pluginInfo.activated, "plugin should not be activated"); + + yield ContentTask.spawn(gNewWindow.gBrowser.selectedBrowser, {}, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); +}); + +add_task(function* () { + // Click the activate button on doorhanger to make sure it works + gNewWindow.PopupNotifications.panel.firstChild._primaryButton.click(); + + let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser); + ok(pluginInfo.activated, "plugin should be activated"); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_hide_overlay.js b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js new file mode 100644 index 000000000..5fab7f6ed --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js @@ -0,0 +1,88 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + + // Tests that the overlay can be hidden for plugins using the close icon. + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon") + let bounds = closeIcon.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + + Assert.ok(!overlay.classList.contains("visible"), "overlay should be hidden."); + }); +}); + +// Test that the overlay cannot be interacted with after the user closes the overlay +add_task(function* () { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon") + let closeIconBounds = closeIcon.getBoundingClientRect(); + let overlayBounds = overlay.getBoundingClientRect(); + let overlayLeft = (overlayBounds.left + overlayBounds.right) / 2; + let overlayTop = (overlayBounds.left + overlayBounds.right) / 2 ; + let closeIconLeft = (closeIconBounds.left + closeIconBounds.right) / 2; + let closeIconTop = (closeIconBounds.top + closeIconBounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + // Simulate clicking on the close icon. + utils.sendMouseEvent("mousedown", closeIconLeft, closeIconTop, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", closeIconLeft, closeIconTop, 0, 1, 0, false, 0, 0); + + // Simulate clicking on the overlay. + utils.sendMouseEvent("mousedown", overlayLeft, overlayTop, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", overlayLeft, overlayTop, 0, 1, 0, false, 0, 0); + + Assert.ok(overlay.hasAttribute("dismissed") && !overlay.classList.contains("visible"), + "Overlay should be hidden"); + }); + + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + + ok(notification.dismissed, "No notification should be shown"); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_iframe.js b/browser/base/content/test/plugins/browser_CTP_iframe.js new file mode 100644 index 000000000..58565559f --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_iframe.js @@ -0,0 +1,48 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_iframe.html"); + + // Tests that the overlays are visible and actionable if the plugin is in an iframe. + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + let frame = content.document.getElementById("frame"); + let doc = frame.contentDocument; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(plugin && overlay.classList.contains("visible"), + "Test 1, Plugin overlay should exist, not be hidden"); + + let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon"); + let bounds = closeIcon.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = doc.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + Assert.ok(!overlay.classList.contains("visible"), + "Test 1, Plugin overlay should exist, be hidden"); + }); +}); + diff --git a/browser/base/content/test/plugins/browser_CTP_multi_allow.js b/browser/base/content/test/plugins/browser_CTP_multi_allow.js new file mode 100644 index 000000000..7bc6aaabf --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_multi_allow.js @@ -0,0 +1,99 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + + // Test that the click-to-play doorhanger for multiple plugins shows the correct + // state when re-opening without reloads or navigation. + + let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser); + ok(!pluginInfo.activated, "plugin should be activated"); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(notification, "Test 1a, Should have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + is(notification.options.pluginData.size, 2, + "Test 1a, Should have two types of plugin in the notification"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + + is(PopupNotifications.panel.firstChild.childNodes.length, 2, "have child nodes"); + + let pluginItem = null; + for (let item of PopupNotifications.panel.firstChild.childNodes) { + is(item.value, "block", "Test 1a, all plugins should start out blocked"); + if (item.action.pluginName == "Test") { + pluginItem = item; + } + } + + // Choose "Allow now" for the test plugin + pluginItem.value = "allownow"; + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser); + ok(pluginInfo.activated, "plugin should be activated"); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(notification, "Test 1b, Should have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + pluginItem = null; + for (let item of PopupNotifications.panel.firstChild.childNodes) { + if (item.action.pluginName == "Test") { + is(item.value, "allownow", "Test 1b, Test plugin should now be set to 'Allow now'"); + } else { + is(item.value, "block", "Test 1b, Second Test plugin should still be blocked"); + pluginItem = item; + } + } + + // Choose "Allow and remember" for the Second Test plugin + pluginItem.value = "allowalways"; + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("secondtestA", gBrowser.selectedBrowser); + ok(pluginInfo.activated, "plugin should be activated"); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(notification, "Test 1c, Should have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + for (let item of PopupNotifications.panel.firstChild.childNodes) { + if (item.action.pluginName == "Test") { + is(item.value, "allownow", "Test 1c, Test plugin should be set to 'Allow now'"); + } else { + is(item.value, "allowalways", "Test 1c, Second Test plugin should be set to 'Allow always'"); + } + } +}); diff --git a/browser/base/content/test/plugins/browser_CTP_nonplugins.js b/browser/base/content/test/plugins/browser_CTP_nonplugins.js new file mode 100644 index 000000000..cdef44d9d --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_nonplugins.js @@ -0,0 +1,58 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_DISABLED, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + + // Test that the click-to-play notification is not shown for non-plugin object elements + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(popupNotification, "Test 1, Should have a click-to-play notification"); + + let pluginRemovedPromise = waitForEvent(gBrowser.selectedBrowser, "PluginRemoved", null, true, true); + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let plugin = content.document.getElementById("secondtestA"); + plugin.parentNode.removeChild(plugin); + plugin = content.document.getElementById("secondtestB"); + plugin.parentNode.removeChild(plugin); + + let image = content.document.createElement("object"); + image.type = "image/png"; + image.data = "moz.png"; + content.document.body.appendChild(image); + }); + yield pluginRemovedPromise; + + popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(popupNotification, "Test 2, Should have a click-to-play notification"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.parentNode.removeChild(plugin); + }); + + popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser); + ok(popupNotification, "Test 3, Should still have a click-to-play notification"); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_notificationBar.js b/browser/base/content/test/plugins/browser_CTP_notificationBar.js new file mode 100644 index 000000000..3c7bd911c --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_notificationBar.js @@ -0,0 +1,151 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); + + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + // Expecting a notification bar for hidden plugins + yield promiseForNotificationBar("plugin-hidden", gTestBrowser); +}); + +add_task(function* () { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null); +}); + +add_task(function* () { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // Expecting a plugin notification bar when plugins are overlaid. + yield promiseForNotificationBar("plugin-hidden", gTestBrowser); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 3b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY"); + }); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 1a, plugin should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!(overlay && overlay.classList.contains("visible")), + "Test 3b, overlay should be hidden."); + }); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_positioned.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // Expecting a plugin notification bar when plugins are overlaid offscreen. + yield promisePopupNotification("click-to-play-plugins"); + yield promiseForNotificationBar("plugin-hidden", gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 4b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY"); + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!(overlay && overlay.classList.contains("visible")), + "Test 4b, overlay should be hidden."); + }); +}); + +// Test that the notification bar is getting dismissed when directly activating plugins +// via the doorhanger. + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // Expecting a plugin notification bar when plugins are overlaid offscreen. + yield promisePopupNotification("click-to-play-plugins"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 6, Plugin should be click-to-play"); + }); + + yield promisePopupNotification("click-to-play-plugins"); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 6, Should have a click-to-play notification"); + + // simulate "always allow" + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + let notificationBox = gBrowser.getNotificationBox(gTestBrowser); + yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 7, plugin should be activated"); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js new file mode 100644 index 000000000..ccb4d11d7 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js @@ -0,0 +1,120 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!popupNotification, "Test 1, Should not have a click-to-play notification"); +}); + +// Test that the click-to-play overlay is not hidden for elements +// partially or fully outside the viewport. + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let doc = content.document; + let p = doc.createElement('embed'); + + p.setAttribute('id', 'test'); + p.setAttribute('type', 'application/x-test'); + p.style.left = "0"; + p.style.bottom = "200px"; + + doc.getElementById('container').appendChild(p); + }); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + let doc = content.document; + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 2, overlay should be visible."); + }); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let doc = content.document; + let p = doc.createElement('embed'); + + p.setAttribute('id', 'test'); + p.setAttribute('type', 'application/x-test'); + p.style.left = "0"; + p.style.bottom = "-410px"; + + doc.getElementById('container').appendChild(p); + }); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let plugin = content.document.getElementById("test"); + let doc = content.document; + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 3, overlay should be visible."); + }); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let doc = content.document; + let p = doc.createElement('embed'); + + p.setAttribute('id', 'test'); + p.setAttribute('type', 'application/x-test'); + p.style.left = "-600px"; + p.style.bottom = "0"; + + doc.getElementById('container').appendChild(p); + }); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + yield ContentTask.spawn(gTestBrowser, null, function* () { + let plugin = content.document.getElementById("test"); + let doc = content.document; + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!(overlay && overlay.classList.contains("visible")), + "Test 4, overlay should be hidden."); + }); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_remove_navigate.js b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js new file mode 100644 index 000000000..8ee1c5b5a --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js @@ -0,0 +1,79 @@ +const gTestRoot = getRootDirectory(gTestPath); +const gHttpTestRoot = gTestRoot.replace("chrome://mochitests/content/", + "http://127.0.0.1:8888/"); + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + }); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); +}); + +/** + * Tests that if a plugin is removed just as we transition to + * a different page, that we don't show the hidden plugin + * notification bar on the new page. + */ +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + + // Load up a page with a plugin... + let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser); + yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html"); + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + yield notificationPromise; + + // Trigger the PluginRemoved event to be fired, and then immediately + // browse to a new page. + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.remove(); + }); + + yield promiseTabLoadEvent(gBrowser.selectedTab, "about:mozilla"); + + // There should be no hidden plugin notification bar at about:mozilla. + let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser); + is(notificationBox.getNotificationWithValue("plugin-hidden"), null, + "Expected no notification box"); +}); + +/** + * Tests that if a plugin is removed just as we transition to + * a different page with a plugin, that we show the right notification + * for the new page. + */ +add_task(function* () { + // Load up a page with a plugin... + let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser); + yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html"); + yield promiseUpdatePluginBindings(gBrowser.selectedBrowser); + yield notificationPromise; + + // Trigger the PluginRemoved event to be fired, and then immediately + // browse to a new page. + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.remove(); + }); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small_2.html"); + let notification = yield waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser); + ok(notification, "There should be a notification shown for the new page."); + // Ensure that the notification is showing information about + // the x-second-test plugin. + let label = notification.label; + ok(label.includes("Second Test"), "Should mention the second plugin"); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_resize.js b/browser/base/content/test/plugins/browser_CTP_resize.js new file mode 100644 index 000000000..9b2a2cd82 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_resize.js @@ -0,0 +1,130 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!popupNotification, "Test 1, Should not have a click-to-play notification"); + + yield promiseTabLoadEvent(newTab, gTestRoot + "plugin_small.html"); // 10x10 plugin + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); +}); + +// Test that the overlay is hidden for "small" plugin elements and is shown +// once they are resized to a size that can hold the overlay +add_task(function* () { + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 2, Should have a click-to-play notification"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!(overlay && overlay.classList.contains("visible")), + "Test 2, overlay should be hidden."); + }); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.style.width = "300px"; + }); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!(overlay && overlay.classList.contains("visible")), + "Test 3, overlay should be hidden."); + }); +}); + + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.style.height = "300px"; + }); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + content.document.getElementById("test").clientTop; + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 4, overlay should be visible."); + }); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.style.width = "10px"; + plugin.style.height = "10px"; + }); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + content.document.getElementById("test").clientTop; + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!(overlay && overlay.classList.contains("visible")), + "Test 5, overlay should be hidden."); + }); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + plugin.style.height = "300px"; + plugin.style.width = "300px"; + }); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + content.document.getElementById("test").clientTop; + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 6, overlay should be visible."); + }); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_zoom.js b/browser/base/content/test/plugins/browser_CTP_zoom.js new file mode 100644 index 000000000..8b353232d --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_zoom.js @@ -0,0 +1,62 @@ +"use strict"; + +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + FullZoom.reset(); // must be called before closing the tab we zoomed! + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!popupNotification, "Test 1, Should not have a click-to-play notification"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); +}); + +// Enlarges the zoom level 4 times and tests that the overlay is +// visible after each enlargement. +add_task(function* () { + for (let count = 0; count < 4; count++) { + + FullZoom.enlarge(); + + // Reload the page + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html"); + yield promiseUpdatePluginBindings(gTestBrowser); + yield ContentTask.spawn(gTestBrowser, { count }, function* (args) { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Overlay should be visible for zoom change count " + args.count); + }); + } +}); + + diff --git a/browser/base/content/test/plugins/browser_blocking.js b/browser/base/content/test/plugins/browser_blocking.js new file mode 100644 index 000000000..334ed9f2e --- /dev/null +++ b/browser/base/content/test/plugins/browser_blocking.js @@ -0,0 +1,349 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +function updateAllTestPlugins(aState) { + setTestPluginEnabledState(aState, "Test Plug-in"); + setTestPluginEnabledState(aState, "Second Test Plug-in"); +} + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + })); +}); + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + Services.prefs.setBoolPref("plugins.click_to_play", true); + + // Prime the content process + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>"); + + // Make sure the blocklist service(s) are running + Components.classes["@mozilla.org/extensions/blocklist;1"] + .getService(Components.interfaces.nsIBlocklistService); + let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser); + ok(!exmsg, "exception: " + exmsg); +}); + +add_task(function* () { + // enable hard blocklisting for the next test + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default"); + + yield promiseForNotificationShown(notification); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED"); + + is(notification.options.pluginData.size, 1, "Test 5: Only the blocked plugin should be present in the notification"); + ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible."); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); +}); + +// Tests a vulnerable, updatable plugin + +add_task(function* () { + // enable hard blocklisting of test + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, + "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE"); + ok(!pluginInfo.activated, "Test 18a, Plugin should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 18a, Plugin overlay should exist, not be hidden"); + + let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink"); + Assert.ok(updateLink.style.visibility != "hidden", + "Test 18a, Plugin should have an update link"); + }); + + let promise = waitForEvent(gBrowser.tabContainer, "TabOpen", null, true); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink"); + let bounds = updateLink.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + yield promise; + + promise = waitForEvent(gBrowser.tabContainer, "TabClose", null, true); + gBrowser.removeCurrentTab(); + yield promise; +}); + +add_task(function* () { + // clicking the update link should not activate the plugin + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, + "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE"); + ok(!pluginInfo.activated, "Test 18b, Plugin should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 18b, Plugin overlay should exist, not be hidden"); + }); +}); + +// Tests a vulnerable plugin with no update +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableNoUpdate.xml", gTestBrowser); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 18c, Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE, + "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE"); + ok(!pluginInfo.activated, "Test 18c, Plugin should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(overlay && overlay.classList.contains("visible"), + "Test 18c, Plugin overlay should exist, not be hidden"); + + let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink"); + Assert.ok(updateLink && updateLink.style.display != "block", + "Test 18c, Plugin should not have an update link"); + }); + + // check that click "Always allow" works with blocked plugins + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE, + "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE"); + ok(pluginInfo.activated, "Test 18c, Plugin should be activated"); + let enabledState = getTestPluginEnabledState(); + ok(enabledState, "Test 18c, Plugin enabled state should be STATE_CLICKTOPLAY"); +}); + +// continue testing "Always allow", make sure it sticks. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 18d, Waited too long for plugin to activate"); + + clearAllPluginPermissions(); +}); + +// clicking the in-content overlay of a vulnerable plugin should bring +// up the notification and not directly activate the plugin +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 18f, Should have a click-to-play notification"); + ok(notification.dismissed, "Test 18f, notification should start dismissed"); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 18f, Waited too long for plugin to activate"); + + var oldEventCallback = notification.options.eventCallback; + let promise = promiseForCondition(() => oldEventCallback == null); + notification.options.eventCallback = function() { + if (oldEventCallback) { + oldEventCallback(); + } + oldEventCallback = null; + }; + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + yield promise; + + ok(notification, "Test 18g, Should have a click-to-play notification"); + ok(!notification.dismissed, "Test 18g, notification should be open"); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated"); +}); + +// Test that "always allow"-ing a plugin will not allow it when it becomes +// blocklisted. +add_task(function* () { + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 24a, Should have a click-to-play notification"); + + // Plugin should start as CTP + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 24a, plugin fallback type should be PLUGIN_CLICK_TO_PLAY"); + ok(!pluginInfo.activated, "Test 24a, Plugin should not be active."); + + // simulate "always allow" + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 24a, Plugin should be active."); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser); +}); + +// the plugin is now blocklisted, so it should not automatically load +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 24b, Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, + "Test 24b, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE"); + ok(!pluginInfo.activated, "Test 24b, Plugin should not be active."); + + // simulate "always allow" + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 24b, Plugin should be active."); + + clearAllPluginPermissions(); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); +}); + +// Plugin sync removal test. Note this test produces a notification drop down since +// the plugin we add has zero dims. +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html"); + + // Maybe there some better trick here, we need to wait for the page load, then + // wait for the js to execute in the page. + yield waitForMs(500); + + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed"); + ok(notification.dismissed, "Test 25: The notification should be dismissed by default"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>"); +}); + +// Tests a page with a blocked plugin in it and make sure the infoURL property +// the blocklist file gets used. +add_task(function* () { + clearAllPluginPermissions(); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + + // Since the plugin notification is dismissed by default, reshow it. + yield promiseForNotificationShown(notification); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, + "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(!objLoadingContent.activated, "Plugin should not be activated."); + }); + + const testUrl = "http://test.url.com/"; + + let firstPanelChild = PopupNotifications.panel.firstChild; + let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid", + "click-to-play-plugins-notification-link"); + is(infoLink.href, testUrl, + "Test 26, the notification URL needs to match the infoURL from the blocklist file."); +}); + diff --git a/browser/base/content/test/plugins/browser_blocklist_content.js b/browser/base/content/test/plugins/browser_blocklist_content.js new file mode 100644 index 000000000..bf4e159bc --- /dev/null +++ b/browser/base/content/test/plugins/browser_blocklist_content.js @@ -0,0 +1,104 @@ +var gTestBrowser = null; +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gChromeRoot = getRootDirectory(gTestPath); + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + })); +}); + +add_task(function* () { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + Services.prefs.setBoolPref("plugins.click_to_play", true); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + + // Prime the blocklist service, the remote service doesn't launch on startup. + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>"); + let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser); + ok(!exmsg, "exception: " + exmsg); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let test = content.document.getElementById("test"); + Assert.ok(test.activated, "task 1a: test plugin should be activated!"); + }); +}); + +// Load a fresh page, load a new plugin blocklist, then load the same page again. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser); + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let test = content.document.getElementById("test"); + ok(!test.activated, "task 2a: test plugin shouldn't activate!"); + }); +}); + +// Unload the block list and lets do this again, only this time lets +// hack around in the content blocklist service maliciously. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>"); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + + // Hack the planet! Load our blocklist shim, so we can mess with blocklist + // return results in the content process. Active until we close our tab. + let mm = gTestBrowser.messageManager; + info("test 3a: loading " + gChromeRoot + "blocklist_proxy.js" + "\n"); + mm.loadFrameScript(gChromeRoot + "blocklist_proxy.js", true); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let test = content.document.getElementById("test"); + Assert.ok(test.activated, "task 3a: test plugin should be activated!"); + }); +}); + +// Load a fresh page, load a new plugin blocklist, then load the same page again. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser); + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let test = content.document.getElementById("test"); + Assert.ok(!test.activated, "task 4a: test plugin shouldn't activate!"); + }); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); +}); diff --git a/browser/base/content/test/plugins/browser_bug743421.js b/browser/base/content/test/plugins/browser_bug743421.js new file mode 100644 index 000000000..966e7b012 --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug743421.js @@ -0,0 +1,119 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + })); +}); + +add_task(function* () { + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + Services.prefs.setBoolPref("plugins.click_to_play", true); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); + + // Prime the blocklist service, the remote service doesn't launch on startup. + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>"); + + let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser); + ok(!exmsg, "exception: " + exmsg); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); +}); + +// Tests that navigation within the page and the window.history API doesn't break click-to-play state. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html"); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 1a, Should not have a click-to-play notification"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin()); + }); + + yield promisePopupNotification("click-to-play-plugins"); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementsByTagName("embed")[0]; + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated"); + }); + + // Click the activate button on doorhanger to make sure it works + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementsByTagName("embed")[0]; + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(objLoadingContent.activated, "Test 1b, Plugin should be activated"); + }); +}); + +add_task(function* () { + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 1c, Should still have a click-to-play notification"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin()); + let plugin = content.document.getElementsByTagName("embed")[1]; + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(objLoadingContent.activated, + "Test 1c, Newly inserted plugin in activated page should be activated"); + }); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementsByTagName("embed")[1]; + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(objLoadingContent.activated, "Test 1d, Plugin should be activated"); + + let promise = ContentTaskUtils.waitForEvent(content, "hashchange"); + content.location += "#anchorNavigation"; + yield promise; + }); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin()); + let plugin = content.document.getElementsByTagName("embed")[2]; + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(objLoadingContent.activated, "Test 1e, Plugin should be activated"); + }); +}); + +add_task(function* () { + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementsByTagName("embed")[2]; + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(objLoadingContent.activated, "Test 1f, Plugin should be activated"); + + content.history.replaceState({}, "", "replacedState"); + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin()); + plugin = content.document.getElementsByTagName("embed")[3]; + objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + Assert.ok(objLoadingContent.activated, "Test 1g, Plugin should be activated"); + }); +}); diff --git a/browser/base/content/test/plugins/browser_bug744745.js b/browser/base/content/test/plugins/browser_bug744745.js new file mode 100644 index 000000000..c9f552a4e --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug744745.js @@ -0,0 +1,50 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gNumPluginBindingsAttached = 0; + +function pluginBindingAttached() { + gNumPluginBindingsAttached++; + if (gNumPluginBindingsAttached != 1) { + ok(false, "if we've gotten here, something is quite wrong"); + } +} + +add_task(function* () { + registerCleanupFunction(function () { + gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true); + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("plugins.click_to_play", true); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true); + + let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug744745.html"); + + yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; }); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("test"); + if (!plugin) { + Assert.ok(false, "plugin element not available."); + return; + } + // We can't use MochiKit's routine + let style = content.getComputedStyle(plugin); + Assert.ok(("opacity" in style) && style.opacity == 1, "plugin style properly configured."); + }); +}); diff --git a/browser/base/content/test/plugins/browser_bug787619.js b/browser/base/content/test/plugins/browser_bug787619.js new file mode 100644 index 000000000..bfd52258c --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug787619.js @@ -0,0 +1,65 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gWrapperClickCount = 0; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug787619.html"); + + // Due to layout being async, "PluginBindAttached" may trigger later. + // This forces a layout flush, thus triggering it, and schedules the + // test so it is definitely executed afterwards. + yield promiseUpdatePluginBindings(gTestBrowser); + + // check plugin state + let pluginInfo = yield promiseForPluginInfo("plugin"); + ok(!pluginInfo.activated, "1a plugin should not be activated"); + + // click the overlay to prompt + let promise = promisePopupNotification("click-to-play-plugins"); + yield ContentTask.spawn(gTestBrowser, {}, function* () { + let plugin = content.document.getElementById("plugin"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + yield promise; + + // check plugin state + pluginInfo = yield promiseForPluginInfo("plugin"); + ok(!pluginInfo.activated, "1b plugin should not be activated"); + + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed && + PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + PopupNotifications.panel.firstChild._primaryButton.click(); + + // check plugin state + pluginInfo = yield promiseForPluginInfo("plugin"); + ok(pluginInfo.activated, "plugin should be activated"); + + is(gWrapperClickCount, 0, 'wrapper should not have received any clicks'); +}); diff --git a/browser/base/content/test/plugins/browser_bug797677.js b/browser/base/content/test/plugins/browser_bug797677.js new file mode 100644 index 000000000..1ae9f5047 --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug797677.js @@ -0,0 +1,43 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gConsoleErrors = 0; + +var Cc = Components.classes; +var Ci = Components.interfaces; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + consoleService.unregisterListener(errorListener); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + let errorListener = { + observe: function(aMessage) { + if (aMessage.message.includes("NS_ERROR_FAILURE")) + gConsoleErrors++; + } + }; + consoleService.registerListener(errorListener); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug797677.html"); + + let pluginInfo = yield promiseForPluginInfo("plugin"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "plugin should not have been found."); + + // simple cpows + yield ContentTask.spawn(gTestBrowser, null, function() { + let plugin = content.document.getElementById("plugin"); + ok(plugin, "plugin should be in the page"); + }); + is(gConsoleErrors, 0, "should have no console errors"); +}); diff --git a/browser/base/content/test/plugins/browser_bug812562.js b/browser/base/content/test/plugins/browser_bug812562.js new file mode 100644 index 000000000..be7b00b22 --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug812562.js @@ -0,0 +1,80 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + Services.prefs.clearUserPref("plugins.click_to_play"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + })); + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + // Prime the blocklist service, the remote service doesn't launch on startup. + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>"); + let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser); + ok(!exmsg, "exception: " + exmsg); +}); + +// Tests that the going back will reshow the notification for click-to-play +// blocklisted plugins +add_task(function* () { + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "test part 1: Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + Assert.ok(!!content.document.getElementById("test"), + "test part 1: plugin should not be activated"); + }); + + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>"); +}); + +add_task(function* () { + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!popupNotification, "test part 2: Should not have a click-to-play notification"); + yield ContentTask.spawn(gTestBrowser, null, function* () { + Assert.ok(!content.document.getElementById("test"), + "test part 2: plugin should not be activated"); + }); + + let obsPromise = TestUtils.topicObserved("PopupNotifications-updateNotShowing"); + let overlayPromise = promisePopupNotification("click-to-play-plugins"); + gTestBrowser.goBack(); + yield obsPromise; + yield overlayPromise; +}); + +add_task(function* () { + yield promiseUpdatePluginBindings(gTestBrowser); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "test part 3: Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + Assert.ok(!!content.document.getElementById("test"), + "test part 3: plugin should not be activated"); + }); +}); diff --git a/browser/base/content/test/plugins/browser_bug818118.js b/browser/base/content/test/plugins/browser_bug818118.js new file mode 100644 index 000000000..9dd6e22e7 --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug818118.js @@ -0,0 +1,40 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_both.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin should be click to play"); + ok(!pluginInfo.activated, "plugin should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, () => { + let unknown = content.document.getElementById("unknown"); + ok(unknown, "should have unknown plugin in page"); + }); +}); diff --git a/browser/base/content/test/plugins/browser_bug820497.js b/browser/base/content/test/plugins/browser_bug820497.js new file mode 100644 index 000000000..b2e0f5268 --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug820497.js @@ -0,0 +1,71 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var gNumPluginBindingsAttached = 0; + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); + + gTestBrowser.addEventListener("PluginBindingAttached", function () { gNumPluginBindingsAttached++ }, true, true); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug820497.html"); + + yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; }); + + yield ContentTask.spawn(gTestBrowser, null, () => { + // Note we add the second plugin in the code farther down, so there's + // no way we got here with anything but one plugin loaded. + let doc = content.document; + let testplugin = doc.getElementById("test"); + ok(testplugin, "should have test plugin"); + let secondtestplugin = doc.getElementById("secondtest"); + ok(!secondtestplugin, "should not yet have second test plugin"); + }); + + yield promisePopupNotification("click-to-play-plugins"); + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "should have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + XPCNativeWrapper.unwrap(content).addSecondPlugin(); + }); + + yield promiseForCondition(function () { return gNumPluginBindingsAttached == 2; }); + + yield ContentTask.spawn(gTestBrowser, null, () => { + let doc = content.document; + let testplugin = doc.getElementById("test"); + ok(testplugin, "should have test plugin"); + let secondtestplugin = doc.getElementById("secondtest"); + ok(secondtestplugin, "should have second test plugin"); + }); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + + ok(notification, "should have popup notification"); + + yield promiseForNotificationShown(notification); + + is(notification.options.pluginData.size, 2, "aited too long for 2 types of plugins in popup notification"); +}); diff --git a/browser/base/content/test/plugins/browser_clearplugindata.html b/browser/base/content/test/plugins/browser_clearplugindata.html new file mode 100644 index 000000000..243350ba4 --- /dev/null +++ b/browser/base/content/test/plugins/browser_clearplugindata.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> + <head> + <title>Plugin Clear Site Data sanitize test</title> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + + <script type="application/javascript"> + function testSteps() + { + // Make sure clearing by timerange is supported. + var p = document.getElementById("plugin1"); + p.setSitesWithDataCapabilities(true); + + p.setSitesWithData( + "foo.com:0:5," + + "bar.com:0:100," + + "baz.com:1:5," + + "qux.com:1:100" + ); + } + </script> + </head> + + <body onload="testSteps();"></body> + +</html> diff --git a/browser/base/content/test/plugins/browser_clearplugindata.js b/browser/base/content/test/plugins/browser_clearplugindata.js new file mode 100644 index 000000000..69d474fed --- /dev/null +++ b/browser/base/content/test/plugins/browser_clearplugindata.js @@ -0,0 +1,127 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); +var gTestBrowser = null; + +// Test clearing plugin data using sanitize.js. +const testURL1 = gTestRoot + "browser_clearplugindata.html"; +const testURL2 = gTestRoot + "browser_clearplugindata_noage.html"; + +var tempScope = {}; +Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tempScope); +var Sanitizer = tempScope.Sanitizer; + +const pluginHostIface = Ci.nsIPluginHost; +var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); +pluginHost.QueryInterface(pluginHostIface); + +var pluginTag = getTestPlugin(); +var sanitizer = null; + +function stored(needles) { + let something = pluginHost.siteHasData(this.pluginTag, null); + if (!needles) + return something; + + if (!something) + return false; + + for (let i = 0; i < needles.length; ++i) { + if (!pluginHost.siteHasData(this.pluginTag, needles[i])) + return false; + } + return true; +} + +add_task(function* () { + registerCleanupFunction(function () { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + if (gTestBrowser) { + gBrowser.removeCurrentTab(); + } + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(function* () { + Services.prefs.setBoolPref("plugins.click_to_play", true); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + + sanitizer = new Sanitizer(); + sanitizer.ignoreTimespan = false; + sanitizer.prefDomain = "privacy.cpd."; + let itemPrefs = gPrefService.getBranch(sanitizer.prefDomain); + itemPrefs.setBoolPref("history", false); + itemPrefs.setBoolPref("downloads", false); + itemPrefs.setBoolPref("cache", false); + itemPrefs.setBoolPref("cookies", true); // plugin data + itemPrefs.setBoolPref("formdata", false); + itemPrefs.setBoolPref("offlineApps", false); + itemPrefs.setBoolPref("passwords", false); + itemPrefs.setBoolPref("sessions", false); + itemPrefs.setBoolPref("siteSettings", false); +}); + +add_task(function* () { + // Load page to set data for the plugin. + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + yield promiseTabLoadEvent(gBrowser.selectedTab, testURL1); + + yield promiseUpdatePluginBindings(gTestBrowser); + + ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]), + "Data stored for sites"); + + // Clear 20 seconds ago + let now_uSec = Date.now() * 1000; + sanitizer.range = [now_uSec - 20*1000000, now_uSec]; + yield sanitizer.sanitize(); + + ok(stored(["bar.com", "qux.com"]), "Data stored for sites"); + ok(!stored(["foo.com"]), "Data cleared for foo.com"); + ok(!stored(["baz.com"]), "Data cleared for baz.com"); + + // Clear everything + sanitizer.range = null; + yield sanitizer.sanitize(); + + ok(!stored(null), "All data cleared"); + + gBrowser.removeCurrentTab(); + gTestBrowser = null; +}); + +add_task(function* () { + // Load page to set data for the plugin. + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + yield promiseTabLoadEvent(gBrowser.selectedTab, testURL2); + + yield promiseUpdatePluginBindings(gTestBrowser); + + ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]), + "Data stored for sites"); + + // Attempt to clear 20 seconds ago. The plugin will throw + // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us + // clearing all data regardless of age. + let now_uSec = Date.now() * 1000; + sanitizer.range = [now_uSec - 20*1000000, now_uSec]; + yield sanitizer.sanitize(); + + ok(!stored(null), "All data cleared"); + + gBrowser.removeCurrentTab(); + gTestBrowser = null; +}); + diff --git a/browser/base/content/test/plugins/browser_clearplugindata_noage.html b/browser/base/content/test/plugins/browser_clearplugindata_noage.html new file mode 100644 index 000000000..820979541 --- /dev/null +++ b/browser/base/content/test/plugins/browser_clearplugindata_noage.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> + <head> + <title>Plugin Clear Site Data sanitize test without age</title> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + + <script type="application/javascript"> + function testSteps() + { + // Make sure clearing by timerange is disabled. + var p = document.getElementById("plugin1"); + p.setSitesWithDataCapabilities(false); + + p.setSitesWithData( + "foo.com:0:5," + + "bar.com:0:100," + + "baz.com:1:5," + + "qux.com:1:100" + ); + } + </script> + </head> + + <body onload="testSteps();"></body> + +</html> diff --git a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js new file mode 100644 index 000000000..bdca32e70 --- /dev/null +++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js @@ -0,0 +1,34 @@ +/** + * Test that the notification bar for crashed GMPs works. + */ +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, function* (browser) { + yield ContentTask.spawn(browser, null, function* () { + const GMP_CRASH_EVENT = { + pluginID: 1, + pluginName: "GlobalTestPlugin", + submittedCrashReport: false, + bubbles: true, + cancelable: true, + gmpPlugin: true, + }; + + let crashEvent = new content.PluginCrashedEvent("PluginCrashed", + GMP_CRASH_EVENT); + content.dispatchEvent(crashEvent); + }); + + let notification = yield waitForNotificationBar("plugin-crashed", browser); + + let notificationBox = gBrowser.getNotificationBox(browser); + ok(notification, "Infobar was shown."); + is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, + "Correct priority."); + is(notification.getAttribute("label"), + "The GlobalTestPlugin plugin has crashed.", + "Correct message."); + }); +}); diff --git a/browser/base/content/test/plugins/browser_pageInfo_plugins.js b/browser/base/content/test/plugins/browser_pageInfo_plugins.js new file mode 100644 index 000000000..0d941e0fa --- /dev/null +++ b/browser/base/content/test/plugins/browser_pageInfo_plugins.js @@ -0,0 +1,191 @@ +var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPageInfo = null; +var gNextTest = null; +var gTestBrowser = null; +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"] + .getService(Components.interfaces.nsIPluginHost); +var gPermissionManager = Components.classes["@mozilla.org/permissionmanager;1"] + .getService(Components.interfaces.nsIPermissionManager); +var gTestPermissionString = gPluginHost.getPermissionStringForType("application/x-test"); +var gSecondTestPermissionString = gPluginHost.getPermissionStringForType("application/x-second-test"); + +function doOnPageLoad(url, continuation) { + gNextTest = continuation; + gTestBrowser.addEventListener("load", pageLoad, true); + gTestBrowser.contentWindow.location = url; +} + +function pageLoad() { + gTestBrowser.removeEventListener("load", pageLoad); + // The plugin events are async dispatched and can come after the load event + // This just allows the events to fire before we then go on to test the states + executeSoon(gNextTest); +} + +function doOnOpenPageInfo(continuation) { + Services.obs.addObserver(pageInfoObserve, "page-info-dialog-loaded", false); + gNextTest = continuation; + // An explanation: it looks like the test harness complains about leaked + // windows if we don't keep a reference to every window we've opened. + // So, don't reuse pointers to opened Page Info windows - simply append + // to this list. + gPageInfo = BrowserPageInfo(null, "permTab"); +} + +function pageInfoObserve(win, topic, data) { + Services.obs.removeObserver(pageInfoObserve, "page-info-dialog-loaded"); + gPageInfo.onFinished.push(() => executeSoon(gNextTest)); +} + +function finishTest() { + gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString); + gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString); + Services.prefs.clearUserPref("plugins.click_to_play"); + gBrowser.removeCurrentTab(); + + gPageInfo = null; + gNextTest = null; + gTestBrowser = null; + gPluginHost = null; + gPermissionManager = null; + + executeSoon(finish); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString); + gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString); + doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart1a); +} + +// The first test plugin is CtP and the second test plugin is enabled. +function testPart1a() { + let test = gTestBrowser.contentDocument.getElementById("test"); + let objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "part 1a: Test plugin should not be activated"); + let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA"); + objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "part 1a: Second Test plugin should be activated"); + + doOnOpenPageInfo(testPart1b); +} + +function testPart1b() { + let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup"); + let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0"); + + is(testRadioGroup.selectedItem, testRadioDefault, "part 1b: Test radio group should be set to 'Default'"); + let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1"); + testRadioGroup.selectedItem = testRadioAllow; + testRadioAllow.doCommand(); + + let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup"); + let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0"); + is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 1b: Second Test radio group should be set to 'Default'"); + let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3"); + secondtestRadioGroup.selectedItem = secondtestRadioAsk; + secondtestRadioAsk.doCommand(); + + doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart2); +} + +// Now, the Test plugin should be allowed, and the Test2 plugin should be CtP +function testPart2() { + let test = gTestBrowser.contentDocument.getElementById("test"). + QueryInterface(Ci.nsIObjectLoadingContent); + ok(test.activated, "part 2: Test plugin should be activated"); + + let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA"). + QueryInterface(Ci.nsIObjectLoadingContent); + ok(!secondtest.activated, "part 2: Second Test plugin should not be activated"); + is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "part 2: Second test plugin should be click-to-play."); + + let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup"); + let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1"); + is(testRadioGroup.selectedItem, testRadioAllow, "part 2: Test radio group should be set to 'Allow'"); + let testRadioBlock = gPageInfo.document.getElementById(gTestPermissionString + "#2"); + testRadioGroup.selectedItem = testRadioBlock; + testRadioBlock.doCommand(); + + let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup"); + let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3"); + is(secondtestRadioGroup.selectedItem, secondtestRadioAsk, "part 2: Second Test radio group should be set to 'Always Ask'"); + let secondtestRadioBlock = gPageInfo.document.getElementById(gSecondTestPermissionString + "#2"); + secondtestRadioGroup.selectedItem = secondtestRadioBlock; + secondtestRadioBlock.doCommand(); + + doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart3); +} + +// Now, all the things should be blocked +function testPart3() { + let test = gTestBrowser.contentDocument.getElementById("test"). + QueryInterface(Ci.nsIObjectLoadingContent); + ok(!test.activated, "part 3: Test plugin should not be activated"); + is(test.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED, + "part 3: Test plugin should be marked as PLUGIN_DISABLED"); + + let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA"). + QueryInterface(Ci.nsIObjectLoadingContent); + + ok(!secondtest.activated, "part 3: Second Test plugin should not be activated"); + is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED, + "part 3: Second test plugin should be marked as PLUGIN_DISABLED"); + + // reset permissions + gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString); + gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString); + // check that changing the permissions affects the radio state in the + // open Page Info window + let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup"); + let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0"); + is(testRadioGroup.selectedItem, testRadioDefault, "part 3: Test radio group should be set to 'Default'"); + let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup"); + let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0"); + is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 3: Second Test radio group should be set to 'Default'"); + + doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart4a); +} + +// Now test that setting permission directly (as from the popup notification) +// immediately influences Page Info. +function testPart4a() { + // simulate "allow" from the doorhanger + gPermissionManager.add(gTestBrowser.currentURI, gTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION); + gPermissionManager.add(gTestBrowser.currentURI, gSecondTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION); + + // check (again) that changing the permissions affects the radio state in the + // open Page Info window + let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup"); + let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1"); + is(testRadioGroup.selectedItem, testRadioAllow, "part 4a: Test radio group should be set to 'Allow'"); + let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup"); + let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1"); + is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4a: Second Test radio group should be set to 'Always Allow'"); + + // now close Page Info and see that it opens with the right settings + gPageInfo.close(); + doOnOpenPageInfo(testPart4b); +} + +// check that "always allow" resulted in the radio buttons getting set to allow +function testPart4b() { + let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup"); + let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1"); + is(testRadioGroup.selectedItem, testRadioAllow, "part 4b: Test radio group should be set to 'Allow'"); + + let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup"); + let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1"); + is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4b: Second Test radio group should be set to 'Allow'"); + + Services.prefs.setBoolPref("plugins.click_to_play", false); + gPageInfo.close(); + finishTest(); +} diff --git a/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js new file mode 100644 index 000000000..ab4743f6f --- /dev/null +++ b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js @@ -0,0 +1,207 @@ +Cu.import("resource://gre/modules/CrashSubmit.jsm", this); + +const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; + +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gTestBrowser = null; +var config = {}; + +add_task(function* () { + // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin + // crash reports. This test needs them enabled. The test also needs a mock + // report server, and fortunately one is already set up by toolkit/ + // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL, + // which CrashSubmit.jsm uses as a server override. + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT"); + let serverUrl = env.get("MOZ_CRASHREPORTER_URL"); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + env.set("MOZ_CRASHREPORTER_URL", SERVER_URL); + + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + // Crash immediately + Services.prefs.setIntPref("dom.ipc.plugins.timeoutSecs", 0); + + registerCleanupFunction(Task.async(function*() { + Services.prefs.clearUserPref("dom.ipc.plugins.timeoutSecs"); + env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); + env.set("MOZ_CRASHREPORTER_URL", serverUrl); + env = null; + config = null; + gTestBrowser = null; + gBrowser.removeCurrentTab(); + window.focus(); + })); +}); + +add_task(function* () { + config = { + shouldSubmissionUIBeVisible: true, + comment: "", + urlOptIn: false + }; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED); + + let pluginCrashed = promisePluginCrashed(); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // Wait for the plugin to crash + yield pluginCrashed; + + let crashReportStatus = TestUtils.topicObserved("crash-report-status", onSubmitStatus); + + // Test that the crash submission UI is actually visible and submit the crash report. + yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) { + let doc = content.document; + let plugin = doc.getElementById("plugin"); + let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit"); + let submitButton = doc.getAnonymousElementByAttribute(plugin, "anonid", "submitButton"); + // Test that we don't send the URL when urlOptIn is false. + doc.getAnonymousElementByAttribute(plugin, "anonid", "submitURLOptIn").checked = aConfig.urlOptIn; + submitButton.click(); + Assert.equal(content.getComputedStyle(pleaseSubmit).display == "block", + aConfig.shouldSubmissionUIBeVisible, "The crash UI should be visible"); + }); + + yield crashReportStatus; +}); + +add_task(function* () { + config = { + shouldSubmissionUIBeVisible: true, + comment: "a test comment", + urlOptIn: true + }; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED); + + let pluginCrashed = promisePluginCrashed(); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // Wait for the plugin to crash + yield pluginCrashed; + + let crashReportStatus = TestUtils.topicObserved("crash-report-status", onSubmitStatus); + + // Test that the crash submission UI is actually visible and submit the crash report. + yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) { + let doc = content.document; + let plugin = doc.getElementById("plugin"); + let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit"); + let submitButton = doc.getAnonymousElementByAttribute(plugin, "anonid", "submitButton"); + // Test that we send the URL when urlOptIn is true. + doc.getAnonymousElementByAttribute(plugin, "anonid", "submitURLOptIn").checked = aConfig.urlOptIn; + doc.getAnonymousElementByAttribute(plugin, "anonid", "submitComment").value = aConfig.comment; + submitButton.click(); + Assert.equal(content.getComputedStyle(pleaseSubmit).display == "block", + aConfig.shouldSubmissionUIBeVisible, "The crash UI should be visible"); + }); + + yield crashReportStatus; +}); + +add_task(function* () { + config = { + shouldSubmissionUIBeVisible: false, + comment: "", + urlOptIn: true + }; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED); + + let pluginCrashed = promisePluginCrashed(); + + // Make sure that the plugin container is too small to display the crash submission UI + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html?" + + encodeURIComponent(JSON.stringify({width: 300, height: 300}))); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + // Wait for the plugin to crash + yield pluginCrashed; + + // Test that the crash submission UI is not visible and do not submit a crash report. + yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) { + let doc = content.document; + let plugin = doc.getElementById("plugin"); + let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit"); + Assert.equal(!!pleaseSubmit && content.getComputedStyle(pleaseSubmit).display == "block", + aConfig.shouldSubmissionUIBeVisible, "Plugin crash UI should not be visible"); + }); +}); + +function promisePluginCrashed() { + return new ContentTask.spawn(gTestBrowser, {}, function* () { + yield new Promise((resolve) => { + addEventListener("PluginCrashReporterDisplayed", function onPluginCrashed() { + removeEventListener("PluginCrashReporterDisplayed", onPluginCrashed); + resolve(); + }); + }); + }) +} + +function onSubmitStatus(aSubject, aData) { + // Wait for success or failed, doesn't matter which. + if (aData != "success" && aData != "failed") + return false; + + let propBag = aSubject.QueryInterface(Ci.nsIPropertyBag); + if (aData == "success") { + let remoteID = getPropertyBagValue(propBag, "serverCrashID"); + ok(!!remoteID, "serverCrashID should be set"); + + // Remove the submitted report file. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + file.initWithPath(Services.crashmanager._submittedDumpsDir); + file.append(remoteID + ".txt"); + ok(file.exists(), "Submitted report file should exist"); + file.remove(false); + } + + let extra = getPropertyBagValue(propBag, "extra"); + ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag"); + + let val = getPropertyBagValue(extra, "PluginUserComment"); + if (config.comment) + is(val, config.comment, + "Comment in extra data should match comment in textbox"); + else + ok(val === undefined, + "Comment should be absent from extra data when textbox is empty"); + + val = getPropertyBagValue(extra, "PluginContentURL"); + if (config.urlOptIn) + is(val, gBrowser.currentURI.spec, + "URL in extra data should match browser URL when opt-in checked"); + else + ok(val === undefined, + "URL should be absent from extra data when opt-in not checked"); + + return true; +} + +function getPropertyBagValue(bag, key) { + try { + var val = bag.getProperty(key); + } + catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + return val; +} diff --git a/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js new file mode 100644 index 000000000..42ef57314 --- /dev/null +++ b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js @@ -0,0 +1,254 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * With e10s, plugins must run in their own process. This means we have + * three processes at a minimum when we're running a plugin: + * + * 1) The main browser, or "chrome" process + * 2) The content process hosting the plugin instance + * 3) The plugin process + * + * If the plugin process crashes, we cannot be sure if the chrome process + * will hear about it first, or the content process will hear about it + * first. Because of how IPC works, that's really up to the operating system, + * and we assume any guarantees about it, so we have to account for both + * possibilities. + * + * This test exercises the browser's reaction to both possibilities. + */ + +const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html"; +const CRASHED_MESSAGE = "BrowserPlugins:NPAPIPluginProcessCrashed"; + +/** + * In order for our test to work, we need to be able to put a plugin + * in a very specific state. Specifically, we need it to match the + * :-moz-handler-crashed pseudoselector. The only way I can find to + * do that is by actually crashing the plugin. So we wait for the + * plugin to crash and show the "please" state (since that will + * only show if both the message from the parent has been received + * AND the PluginCrashed event has fired). + * + * Once in that state, we try to rewind the clock a little bit - we clear + * out the crashData cache in the PluginContent with a message, and we also + * override the pluginFallbackState of the <object> to fool PluginContent + * into believing that the plugin is in a particular state. + * + * @param browser + * The browser that has loaded the CRASH_URL that we need to + * prepare to be in the special state. + * @param pluginFallbackState + * The value we should override the <object>'s pluginFallbackState + * with. + * @return Promise + * The Promise resolves when the plugin has officially been put into + * the crash reporter state, and then "rewound" to have the "status" + * attribute of the statusDiv removed. The resolved Promise returns + * the run ID for the crashed plugin. It rejects if we never get into + * the crash reporter state. + */ +function preparePlugin(browser, pluginFallbackState) { + return ContentTask.spawn(browser, pluginFallbackState, function* (pluginFallbackState) { + let plugin = content.document.getElementById("plugin"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + // CRASH_URL will load a plugin that crashes immediately. We + // wait until the plugin has finished being put into the crash + // state. + let statusDiv; + yield ContentTaskUtils.waitForCondition(() => { + statusDiv = plugin.ownerDocument + .getAnonymousElementByAttribute(plugin, "anonid", + "submitStatus"); + return statusDiv && statusDiv.getAttribute("status") == "please"; + }, "Timed out waiting for plugin to be in crash report state"); + + // "Rewind", by wiping out the status attribute... + statusDiv.removeAttribute("status"); + // Somehow, I'm able to get away with overriding the getter for + // this XPCOM object. Probably because I've got chrome privledges. + Object.defineProperty(plugin, "pluginFallbackType", { + get: function() { + return pluginFallbackState; + } + }); + return plugin.runID; + }).then((runID) => { + browser.messageManager.sendAsyncMessage("BrowserPlugins:Test:ClearCrashData"); + return runID; + }); +} + +add_task(function* setup() { + // Bypass click-to-play + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED); + + // Clear out any minidumps we create from plugins - we really don't care + // about them. + let crashObserver = (subject, topic, data) => { + if (topic != "plugin-crashed") { + return; + } + + let propBag = subject.QueryInterface(Ci.nsIPropertyBag2); + let minidumpID = propBag.getPropertyAsAString("pluginDumpID"); + + let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + minidumpDir.append("minidumps"); + + let pluginDumpFile = minidumpDir.clone(); + pluginDumpFile.append(minidumpID + ".dmp"); + + let extraFile = minidumpDir.clone(); + extraFile.append(minidumpID + ".extra"); + + ok(pluginDumpFile.exists(), "Found minidump"); + ok(extraFile.exists(), "Found extra file"); + + pluginDumpFile.remove(false); + extraFile.remove(false); + }; + + Services.obs.addObserver(crashObserver, "plugin-crashed", false); + // plugins.testmode will make BrowserPlugins:Test:ClearCrashData work. + Services.prefs.setBoolPref("plugins.testmode", true); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("plugins.testmode"); + Services.obs.removeObserver(crashObserver, "plugin-crashed"); + }); +}); + +/** + * In this case, the chrome process hears about the crash first. + */ +add_task(function* testChromeHearsPluginCrashFirst() { + // Open a remote window so that we can run this test even if e10s is not + // enabled by default. + let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true}); + let browser = win.gBrowser.selectedBrowser; + + browser.loadURI(CRASH_URL); + yield BrowserTestUtils.browserLoaded(browser); + + // In this case, we want the <object> to match the -moz-handler-crashed + // pseudoselector, but we want it to seem still active, because the + // content process is not yet supposed to know that the plugin has + // crashed. + let runID = yield preparePlugin(browser, + Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE); + + // Send the message down to PluginContent.jsm saying that the plugin has + // crashed, and that we have a crash report. + let mm = browser.messageManager; + mm.sendAsyncMessage(CRASHED_MESSAGE, + { pluginName: "", runID, state: "please" }); + + yield ContentTask.spawn(browser, null, function* () { + // At this point, the content process should have heard the + // plugin crash message from the parent, and we are OK to emit + // the PluginCrashed event. + let plugin = content.document.getElementById("plugin"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + let statusDiv = plugin.ownerDocument + .getAnonymousElementByAttribute(plugin, "anonid", + "submitStatus"); + + if (statusDiv.getAttribute("status") == "please") { + Assert.ok(false, "Did not expect plugin to be in crash report mode yet."); + return; + } + + // Now we need the plugin to seem crashed to PluginContent.jsm, without + // actually crashing the plugin again. We hack around this by overriding + // the pluginFallbackType again. + Object.defineProperty(plugin, "pluginFallbackType", { + get: function() { + return Ci.nsIObjectLoadingContent.PLUGIN_CRASHED; + }, + }); + + let event = new content.PluginCrashedEvent("PluginCrashed", { + pluginName: "", + pluginDumpID: "", + browserDumpID: "", + submittedCrashReport: false, + bubbles: true, + cancelable: true, + }); + + plugin.dispatchEvent(event); + Assert.equal(statusDiv.getAttribute("status"), "please", + "Should have been showing crash report UI"); + }); + yield BrowserTestUtils.closeWindow(win); +}); + +/** + * In this case, the content process hears about the crash first. + */ +add_task(function* testContentHearsCrashFirst() { + // Open a remote window so that we can run this test even if e10s is not + // enabled by default. + let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true}); + let browser = win.gBrowser.selectedBrowser; + + browser.loadURI(CRASH_URL); + yield BrowserTestUtils.browserLoaded(browser); + + // In this case, we want the <object> to match the -moz-handler-crashed + // pseudoselector, and we want the plugin to seem crashed, since the + // content process in this case has heard about the crash first. + let runID = yield preparePlugin(browser, + Ci.nsIObjectLoadingContent.PLUGIN_CRASHED); + + yield ContentTask.spawn(browser, null, function* () { + // At this point, the content process has not yet heard from the + // parent about the crash report. Let's ensure that by making sure + // we're not showing the plugin crash report UI. + let plugin = content.document.getElementById("plugin"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + let statusDiv = plugin.ownerDocument + .getAnonymousElementByAttribute(plugin, "anonid", + "submitStatus"); + + if (statusDiv.getAttribute("status") == "please") { + Assert.ok(false, "Did not expect plugin to be in crash report mode yet."); + } + + let event = new content.PluginCrashedEvent("PluginCrashed", { + pluginName: "", + pluginDumpID: "", + browserDumpID: "", + submittedCrashReport: false, + bubbles: true, + cancelable: true, + }); + + plugin.dispatchEvent(event); + + Assert.notEqual(statusDiv.getAttribute("status"), "please", + "Should not yet be showing crash report UI"); + }); + + // Now send the message down to PluginContent.jsm that the plugin has + // crashed... + let mm = browser.messageManager; + mm.sendAsyncMessage(CRASHED_MESSAGE, + { pluginName: "", runID, state: "please"}); + + yield ContentTask.spawn(browser, null, function* () { + // At this point, the content process will have heard the message + // from the parent and reacted to it. We should be showing the plugin + // crash report UI now. + let plugin = content.document.getElementById("plugin"); + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + let statusDiv = plugin.ownerDocument + .getAnonymousElementByAttribute(plugin, "anonid", + "submitStatus"); + + Assert.equal(statusDiv.getAttribute("status"), "please", + "Should have been showing crash report UI"); + }); + + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/base/content/test/plugins/browser_plugin_reloading.js b/browser/base/content/test/plugins/browser_plugin_reloading.js new file mode 100644 index 000000000..7327d4cf9 --- /dev/null +++ b/browser/base/content/test/plugins/browser_plugin_reloading.js @@ -0,0 +1,85 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); +var gTestBrowser = null; + +function updateAllTestPlugins(aState) { + setTestPluginEnabledState(aState, "Test Plug-in"); + setTestPluginEnabledState(aState, "Second Test Plug-in"); +} + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + gTestBrowser = null; + gBrowser.removeCurrentTab(); + window.focus(); + })); +}); + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + Services.prefs.setBoolPref("plugins.click_to_play", true); + + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + // Prime the blocklist service, the remote service doesn't launch on startup. + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>"); + let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser); + ok(!exmsg, "exception: " + exmsg); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); +}); + +// Tests that a click-to-play plugin retains its activated state upon reloading +add_task(function* () { + clearAllPluginPermissions(); + + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 1, Should have a click-to-play notification"); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 2, plugin fallback type should be PLUGIN_CLICK_TO_PLAY"); + + // run the plugin + yield promisePlayObject("test"); + + yield promiseUpdatePluginBindings(gTestBrowser); + + pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 3, plugin should have started"); + ok(pluginInfo.activated, "Test 4, plugin node should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let plugin = content.document.getElementById("test"); + let npobj1 = Components.utils.waiveXrays(plugin).getObjectValue(); + plugin.src = plugin.src; + let pluginsDiffer = false; + try { + Components.utils.waiveXrays(plugin).checkObjectValue(npobj1); + } catch (e) { + pluginsDiffer = true; + } + + Assert.ok(pluginsDiffer, "Test 5, plugins differ."); + }); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 6, Plugin should have retained activated state."); + is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 7, plugin should have started"); +}); diff --git a/browser/base/content/test/plugins/browser_pluginnotification.js b/browser/base/content/test/plugins/browser_pluginnotification.js new file mode 100644 index 000000000..bf32f37a4 --- /dev/null +++ b/browser/base/content/test/plugins/browser_pluginnotification.js @@ -0,0 +1,626 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); +var gTestBrowser = null; + +function updateAllTestPlugins(aState) { + setTestPluginEnabledState(aState, "Test Plug-in"); + setTestPluginEnabledState(aState, "Second Test Plug-in"); +} + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + gTestBrowser = null; + gBrowser.removeCurrentTab(); + window.focus(); + })); +}); + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + Services.prefs.setBoolPref("plugins.click_to_play", true); + + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + // Prime the blocklist service, the remote service doesn't launch on startup. + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>"); + let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser); + ok(!exmsg, "exception: " + exmsg); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); +}); + +// Tests a page with an unknown plugin in it. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_unknown.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let pluginInfo = yield promiseForPluginInfo("unknown"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, + "Test 1a, plugin fallback type should be PLUGIN_UNSUPPORTED"); +}); + +// Test that the doorhanger is shown when the user clicks on the overlay +// after having previously blocked the plugin. +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Plugin should not be activated"); + + // Simulate clicking the "Allow Now" button. + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._secondaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Plugin should be activated"); + + // Simulate clicking the "Block" button. + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Plugin should not be activated"); + + // Simulate clicking the overlay + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let bounds = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + ok(!notification.dismissed, "A plugin notification should be shown."); + + clearAllPluginPermissions(); +}); + +// Tests that going back will reshow the notification for click-to-play plugins +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>"); + + // make sure the notification is gone + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 11b, Should not have a click-to-play notification"); + + gTestBrowser.webNavigation.goBack(); + + yield promisePopupNotification("click-to-play-plugins"); +}); + +// Tests that the "Allow Always" permission works for click-to-play plugins +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 12a, Plugin should not be activated"); + + // Simulate clicking the "Allow Always" button. + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + + yield promiseForNotificationShown(notification); + + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 12a, Plugin should be activated"); +}); + +// Test that the "Always" permission, when set for just the Test plugin, +// does not also allow the Second Test plugin. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + yield promisePopupNotification("click-to-play-plugins"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let test = content.document.getElementById("test"); + let secondtestA = content.document.getElementById("secondtestA"); + let secondtestB = content.document.getElementById("secondtestB"); + Assert.ok(test.activated && !secondtestA.activated && !secondtestB.activated, + "Content plugins are set up"); + }); + + clearAllPluginPermissions(); +}); + +// Tests that the plugin's "activated" property is true for working plugins +// with click-to-play disabled. +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test2.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let pluginInfo = yield promiseForPluginInfo("test1"); + ok(pluginInfo.activated, "Test 14, Plugin should be activated"); +}); + +// Tests that the overlay is shown instead of alternate content when +// plugins are click to play. +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_alternate_content.html"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!!mainBox, "Test 15, Plugin overlay should exist"); + }); +}); + +// Tests that mContentType is used for click-to-play plugins, and not the +// inspected type. +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug749455.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 17, Should have a click-to-play notification"); +}); + +// Tests that clicking the icon of the overlay activates the doorhanger +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated"); + + ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, + "Test 19a, Doorhanger should start out dismissed"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let icon = doc.getAnonymousElementByAttribute(plugin, "class", "icon"); + let bounds = icon.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed; + yield promiseForCondition(condition); +}); + +// Tests that clicking the text of the overlay activates the plugin +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated"); + + ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, + "Test 19c, Doorhanger should start out dismissed"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let text = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay"); + let bounds = text.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed; + yield promiseForCondition(condition); +}); + +// Tests that clicking the box of the overlay activates the doorhanger +// (just to be thorough) +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated"); + + ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, + "Test 19e, Doorhanger should start out dismissed"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", 50, 50, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", 50, 50, 0, 1, 0, false, 0, 0); + }); + + let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed && + PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 19e, Plugin should not be activated"); + + clearAllPluginPermissions(); +}); + +// Tests that a plugin in a div that goes from style="display: none" to +// "display: block" can be clicked to activate. +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_hidden_to_visible.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 20a, Should have a click-to-play notification"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + Assert.ok(!!overlay, "Test 20a, Plugin overlay should exist"); + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + let overlayRect = mainBox.getBoundingClientRect(); + Assert.ok(overlayRect.width == 0 && overlayRect.height == 0, + "Test 20a, plugin should have an overlay with 0px width and height"); + }); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 20b, plugin should not be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let div = doc.getElementById("container"); + Assert.equal(div.style.display, "none", + "Test 20b, container div should be display: none"); + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let div = doc.getElementById("container"); + div.style.display = "block"; + }); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + let overlayRect = mainBox.getBoundingClientRect(); + Assert.ok(overlayRect.width == 200 && overlayRect.height == 200, + "Test 20c, plugin should have overlay dims of 200px"); + }); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(!pluginInfo.activated, "Test 20b, plugin should not be activated"); + + ok(notification.dismissed, "Test 20c, Doorhanger should start out dismissed"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let bounds = plugin.getBoundingClientRect(); + let left = (bounds.left + bounds.right) / 2; + let top = (bounds.top + bounds.bottom) / 2; + let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + let condition = () => !notification.dismissed && !!PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 20c, plugin should be activated"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect(); + Assert.ok(overlayRect.width == 0 && overlayRect.height == 0, + "Test 20c, plugin should have overlay dims of 0px"); + }); + + clearAllPluginPermissions(); +}); + +// Test having multiple different types of plugin on one page +add_task(function* () { + // contains three plugins, application/x-test, application/x-second-test x 2 + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 21a, Should have a click-to-play notification"); + + // confirm all three are blocked by ctp at this point + let ids = ["test", "secondtestA", "secondtestB"]; + for (let id of ids) { + yield ContentTask.spawn(gTestBrowser, { id }, function* (args) { + let doc = content.document; + let plugin = doc.getElementById(args.id); + let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect(); + Assert.ok(overlayRect.width == 200 && overlayRect.height == 200, + "Test 21a, plugin " + args.id + " should have click-to-play overlay with dims"); + }); + + let pluginInfo = yield promiseForPluginInfo(id); + ok(!pluginInfo.activated, "Test 21a, Plugin with id=" + id + " should not be activated"); + } + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 21a, Should have a click-to-play notification"); + + // we have to actually show the panel to get the bindings to instantiate + yield promiseForNotificationShown(notification); + + is(notification.options.pluginData.size, 2, "Test 21a, Should have two types of plugin in the notification"); + + let centerAction = null; + for (let action of notification.options.pluginData.values()) { + if (action.pluginName == "Test") { + centerAction = action; + break; + } + } + ok(centerAction, "Test 21b, found center action for the Test plugin"); + + let centerItem = null; + for (let item of PopupNotifications.panel.firstChild.childNodes) { + is(item.value, "block", "Test 21b, all plugins should start out blocked"); + if (item.action == centerAction) { + centerItem = item; + break; + } + } + ok(centerItem, "Test 21b, found center item for the Test plugin"); + + // Select the allow now option in the select drop down for Test Plugin + centerItem.value = "allownow"; + + // "click" the button to activate the Test plugin + PopupNotifications.panel.firstChild._primaryButton.click(); + + let pluginInfo = yield promiseForPluginInfo("test"); + ok(pluginInfo.activated, "Test 21b, plugin should be activated"); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 21b, Should have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + ok(notification.options.pluginData.size == 2, "Test 21c, Should have one type of plugin in the notification"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect(); + Assert.ok(overlayRect.width == 0 && overlayRect.height == 0, + "Test 21c, plugin should have overlay dims of 0px"); + }); + + ids = ["secondtestA", "secondtestB"]; + for (let id of ids) { + yield ContentTask.spawn(gTestBrowser, { id }, function* (args) { + let doc = content.document; + let plugin = doc.getElementById(args.id); + let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect(); + Assert.ok(overlayRect.width == 200 && overlayRect.height == 200, + "Test 21c, plugin " + args.id + " should have click-to-play overlay with zero dims"); + }); + + + let pluginInfo = yield promiseForPluginInfo(id); + ok(!pluginInfo.activated, "Test 21c, Plugin with id=" + id + " should not be activated"); + } + + centerAction = null; + for (let action of notification.options.pluginData.values()) { + if (action.pluginName == "Second Test") { + centerAction = action; + break; + } + } + ok(centerAction, "Test 21d, found center action for the Second Test plugin"); + + centerItem = null; + for (let item of PopupNotifications.panel.firstChild.childNodes) { + if (item.action == centerAction) { + is(item.value, "block", "Test 21d, test plugin 2 should start blocked"); + centerItem = item; + break; + } + else { + is(item.value, "allownow", "Test 21d, test plugin should be enabled"); + } + } + ok(centerItem, "Test 21d, found center item for the Second Test plugin"); + + // Select the allow now option in the select drop down for Second Test Plguins + centerItem.value = "allownow"; + + // "click" the button to activate the Second Test plugins + PopupNotifications.panel.firstChild._primaryButton.click(); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 21d, Should have a click-to-play notification"); + + ids = ["test", "secondtestA", "secondtestB"]; + for (let id of ids) { + yield ContentTask.spawn(gTestBrowser, { id }, function* (args) { + let doc = content.document; + let plugin = doc.getElementById(args.id); + let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect(); + Assert.ok(overlayRect.width == 0 && overlayRect.height == 0, + "Test 21d, plugin " + args.id + " should have click-to-play overlay with zero dims"); + }); + + let pluginInfo = yield promiseForPluginInfo(id); + ok(pluginInfo.activated, "Test 21d, Plugin with id=" + id + " should not be activated"); + } +}); + +// Tests that a click-to-play plugin resets its activated state when changing types +add_task(function* () { + clearAllPluginPermissions(); + + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 22, Should have a click-to-play notification"); + + // Plugin should start as CTP + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + plugin.type = null; + // We currently don't properly change state just on type change, + // so rebind the plugin to tree. bug 767631 + plugin.parentNode.appendChild(plugin); + }); + + pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL"); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let doc = content.document; + let plugin = doc.getElementById("test"); + plugin.type = "application/x-test"; + plugin.parentNode.appendChild(plugin); + }); + + pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, + "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY"); + ok(!pluginInfo.activated, "Test 23, plugin node should not be activated"); +}); + +// Plugin sync removal test. Note this test produces a notification drop down since +// the plugin we add has zero dims. +add_task(function* () { + updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html"); + + // Maybe there some better trick here, we need to wait for the page load, then + // wait for the js to execute in the page. + yield waitForMs(500); + + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed"); + ok(notification.dismissed, "Test 25: The notification should be dismissed by default"); + + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>"); +}); + +// Tests a page with a blocked plugin in it and make sure the infoURL property +// the blocklist file gets used. +add_task(function* () { + clearAllPluginPermissions(); + + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html"); + + // Work around for delayed PluginBindingAttached + yield promiseUpdatePluginBindings(gTestBrowser); + + let notification = PopupNotifications.getNotification("click-to-play-plugins"); + + // Since the plugin notification is dismissed by default, reshow it. + yield promiseForNotificationShown(notification); + + let pluginInfo = yield promiseForPluginInfo("test"); + is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, + "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED"); + ok(!pluginInfo.activated, "Plugin should be activated."); + + const testUrl = "http://test.url.com/"; + + let firstPanelChild = PopupNotifications.panel.firstChild; + let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid", + "click-to-play-plugins-notification-link"); + is(infoLink.href, testUrl, + "Test 26, the notification URL needs to match the infoURL from the blocklist file."); +}); diff --git a/browser/base/content/test/plugins/browser_plugins_added_dynamically.js b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js new file mode 100644 index 000000000..22077a54d --- /dev/null +++ b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js @@ -0,0 +1,137 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://mochi.test:8888/"); +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +var gTestBrowser = null; + +add_task(function* () { + registerCleanupFunction(Task.async(function*() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.click_to_play"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser); + resetBlocklist(); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + })); +}); + +// "Activate" of a given type -> plugins of that type dynamically added should +// automatically play. +add_task(function* () { + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + Services.prefs.setBoolPref("plugins.click_to_play", true); + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); +}); + +add_task(function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html"); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 1a, Should not have a click-to-play notification"); + + // Add a plugin of type test + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test")); + }); + + yield promisePopupNotification("click-to-play-plugins"); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 1a, Should not have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + let centerAction = null; + for (let action of notification.options.pluginData.values()) { + if (action.pluginName == "Test") { + centerAction = action; + break; + } + } + ok(centerAction, "Test 2, found center action for the Test plugin"); + + let centerItem = null; + for (let item of PopupNotifications.panel.firstChild.childNodes) { + is(item.value, "block", "Test 3, all plugins should start out blocked"); + if (item.action == centerAction) { + centerItem = item; + break; + } + } + ok(centerItem, "Test 4, found center item for the Test plugin"); + + // Select the allow now option in the select drop down for Test Plugin + centerItem.value = "allownow"; + + // "click" the button to activate the Test plugin + PopupNotifications.panel.firstChild._primaryButton.click(); + + let pluginInfo = yield promiseForPluginInfo("pluginone"); + ok(pluginInfo.activated, "Test 5, plugin should be activated"); + + // Add another plugin of type test + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-test")); + }); + + pluginInfo = yield promiseForPluginInfo("plugintwo"); + ok(pluginInfo.activated, "Test 6, plugins should be activated"); +}); + +// "Activate" of a given type -> plugins of other types dynamically added +// should not automatically play. +add_task(function* () { + clearAllPluginPermissions(); + + yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html"); + + let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 7, Should not have a click-to-play notification"); + + // Add a plugin of type test + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test")); + }); + + yield promisePopupNotification("click-to-play-plugins"); + + notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 8, Should not have a click-to-play notification"); + + yield promiseForNotificationShown(notification); + + is(notification.options.pluginData.size, 1, "Should be one plugin action"); + + let pluginInfo = yield promiseForPluginInfo("pluginone"); + ok(!pluginInfo.activated, "Test 8, test plugin should be activated"); + + let condition = () => !notification.dismissed && + PopupNotifications.panel.firstChild; + yield promiseForCondition(condition); + + // "click" the button to activate the Test plugin + PopupNotifications.panel.firstChild._primaryButton.click(); + + pluginInfo = yield promiseForPluginInfo("pluginone"); + ok(pluginInfo.activated, "Test 9, test plugin should be activated"); + + yield ContentTask.spawn(gTestBrowser, {}, function* () { + new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-second-test")); + }); + + yield promisePopupNotification("click-to-play-plugins"); + + pluginInfo = yield promiseForPluginInfo("pluginone"); + ok(pluginInfo.activated, "Test 10, plugins should be activated"); + pluginInfo = yield promiseForPluginInfo("plugintwo"); + ok(!pluginInfo.activated, "Test 11, plugins should be activated"); +}); diff --git a/browser/base/content/test/plugins/browser_private_clicktoplay.js b/browser/base/content/test/plugins/browser_private_clicktoplay.js new file mode 100644 index 000000000..785b1bb31 --- /dev/null +++ b/browser/base/content/test/plugins/browser_private_clicktoplay.js @@ -0,0 +1,216 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir; +const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +var gTestBrowser = null; +var gNextTest = null; +var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var gPrivateWindow = null; +var gPrivateBrowser = null; + +function finishTest() { + clearAllPluginPermissions(); + gBrowser.removeCurrentTab(); + if (gPrivateWindow) { + gPrivateWindow.close(); + } + window.focus(); +} + +let createPrivateWindow = Task.async(function* createPrivateWindow(url) { + gPrivateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + ok(!!gPrivateWindow, "should have created a private window."); + gPrivateBrowser = gPrivateWindow.getBrowser().selectedBrowser; + + BrowserTestUtils.loadURI(gPrivateBrowser, url); + yield BrowserTestUtils.browserLoaded(gPrivateBrowser); +}); + +add_task(function* test() { + registerCleanupFunction(function() { + clearAllPluginPermissions(); + getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED; + getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED; + }); + + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + let promise = BrowserTestUtils.browserLoaded(gTestBrowser); + + Services.prefs.setBoolPref("plugins.click_to_play", true); + getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY; + getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY; + yield promise; +}); + +add_task(function* test1a() { + yield createPrivateWindow(gHttpTestRoot + "plugin_test.html"); +}); + +add_task(function* test1b() { + let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser); + ok(popupNotification, "Test 1b, Should have a click-to-play notification"); + + yield ContentTask.spawn(gPrivateBrowser, null, function() { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated"); + }); + + // Check the button status + let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel, + "Shown"); + popupNotification.reshow(); + + yield promiseShown; + let button1 = gPrivateWindow.PopupNotifications.panel.firstChild._primaryButton; + let button2 = gPrivateWindow.PopupNotifications.panel.firstChild._secondaryButton; + is(button1.getAttribute("action"), "_singleActivateNow", "Test 1b, Blocked plugin in private window should have a activate now button"); + ok(button2.hidden, "Test 1b, Blocked plugin in a private window should not have a secondary button") + + gPrivateWindow.close(); + BrowserTestUtils.loadURI(gTestBrowser, gHttpTestRoot + "plugin_test.html"); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* test2a() { + // enable test plugin on this site + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 2a, Should have a click-to-play notification"); + + yield ContentTask.spawn(gTestBrowser, null, function() { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 2a, Plugin should not be activated"); + }); + + // Simulate clicking the "Allow Now" button. + let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, + "Shown"); + popupNotification.reshow(); + yield promiseShown; + + PopupNotifications.panel.firstChild._secondaryButton.click(); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + let condition = () => objLoadingContent.activated; + yield ContentTaskUtils.waitForCondition(condition, "Test 2a, Waited too long for plugin to activate"); + }); +}); + +add_task(function* test2c() { + let topicObserved = TestUtils.topicObserved("PopupNotifications-updateNotShowing"); + yield createPrivateWindow(gHttpTestRoot + "plugin_test.html"); + yield topicObserved; + + let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser); + ok(popupNotification, "Test 2c, Should have a click-to-play notification"); + + yield ContentTask.spawn(gPrivateBrowser, null, function() { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 2c, Plugin should be activated"); + }); + + // Check the button status + let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel, + "Shown"); + popupNotification.reshow(); + yield promiseShown; + let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer; + ok(buttonContainer.hidden, "Test 2c, Activated plugin in a private window should not have visible buttons"); + + clearAllPluginPermissions(); + gPrivateWindow.close(); + + BrowserTestUtils.loadURI(gTestBrowser, gHttpTestRoot + "plugin_test.html"); + yield BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(function* test3a() { + // enable test plugin on this site + let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 3a, Should have a click-to-play notification"); + + yield ContentTask.spawn(gTestBrowser, null, function() { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 3a, Plugin should not be activated"); + }); + + // Simulate clicking the "Allow Always" button. + let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, + "Shown"); + popupNotification.reshow(); + yield promiseShown; + PopupNotifications.panel.firstChild._secondaryButton.click(); + + yield ContentTask.spawn(gTestBrowser, null, function* () { + let plugin = content.document.getElementById("test"); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + let condition = () => objLoadingContent.activated; + yield ContentTaskUtils.waitForCondition(condition, "Test 3a, Waited too long for plugin to activate"); + }); +}); + +add_task(function* test3c() { + let topicObserved = TestUtils.topicObserved("PopupNotifications-updateNotShowing"); + yield createPrivateWindow(gHttpTestRoot + "plugin_test.html"); + yield topicObserved; + + let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser); + ok(popupNotification, "Test 3c, Should have a click-to-play notification"); + + // Check the button status + let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel, + "Shown"); + popupNotification.reshow(); + yield promiseShown; + let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer; + ok(buttonContainer.hidden, "Test 3c, Activated plugin in a private window should not have visible buttons"); + + BrowserTestUtils.loadURI(gPrivateBrowser, gHttpTestRoot + "plugin_two_types.html"); + yield BrowserTestUtils.browserLoaded(gPrivateBrowser); +}); + +add_task(function* test3d() { + let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser); + ok(popupNotification, "Test 3d, Should have a click-to-play notification"); + + // Check the list item status + let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel, + "Shown"); + popupNotification.reshow(); + yield promiseShown; + let doc = gPrivateWindow.document; + for (let item of gPrivateWindow.PopupNotifications.panel.firstChild.childNodes) { + let allowalways = doc.getAnonymousElementByAttribute(item, "anonid", "allowalways"); + ok(allowalways, "Test 3d, should have list item for allow always"); + let allownow = doc.getAnonymousElementByAttribute(item, "anonid", "allownow"); + ok(allownow, "Test 3d, should have list item for allow now"); + let block = doc.getAnonymousElementByAttribute(item, "anonid", "block"); + ok(block, "Test 3d, should have list item for block"); + + if (item.action.pluginName === "Test") { + is(item.value, "allowalways", "Test 3d, Plugin should bet set to 'allow always'"); + ok(!allowalways.hidden, "Test 3d, Plugin set to 'always allow' should have a visible 'always allow' action."); + ok(allownow.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'allow now' action."); + ok(block.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'block' action."); + } else if (item.action.pluginName === "Second Test") { + is(item.value, "block", "Test 3d, Second plugin should bet set to 'block'"); + ok(allowalways.hidden, "Test 3d, Plugin set to 'block' should have a visible 'always allow' action."); + ok(!allownow.hidden, "Test 3d, Plugin set to 'block' should have a visible 'allow now' action."); + ok(!block.hidden, "Test 3d, Plugin set to 'block' should have a visible 'block' action."); + } else { + ok(false, "Test 3d, Unexpected plugin '"+item.action.pluginName+"'"); + } + } + + finishTest(); +}); diff --git a/browser/base/content/test/plugins/head.js b/browser/base/content/test/plugins/head.js new file mode 100644 index 000000000..4995c4dc6 --- /dev/null +++ b/browser/base/content/test/plugins/head.js @@ -0,0 +1,396 @@ +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; + } + }); +} diff --git a/browser/base/content/test/plugins/plugin_add_dynamically.html b/browser/base/content/test/plugins/plugin_add_dynamically.html new file mode 100644 index 000000000..863d36e09 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_add_dynamically.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<script> +function addPlugin(aId, aType="application/x-test") { + var embed = document.createElement("embed"); + embed.setAttribute("id", aId); + embed.style.width = "200px"; + embed.style.height = "200px"; + embed.setAttribute("type", aType); + return document.body.appendChild(embed); +} +</script> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_alternate_content.html b/browser/base/content/test/plugins/plugin_alternate_content.html new file mode 100644 index 000000000..f8acc833c --- /dev/null +++ b/browser/base/content/test/plugins/plugin_alternate_content.html @@ -0,0 +1,9 @@ +<!-- bug 739575 --> +<html> +<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> +</head> +<body> +<object id="test" type="application/x-test" style="height: 200px; width:200px"> +<p><a href="about:blank">you should not see this link when plugins are click-to-play</a></p> +</object> +</body></html> diff --git a/browser/base/content/test/plugins/plugin_big.html b/browser/base/content/test/plugins/plugin_big.html new file mode 100644 index 000000000..d11506176 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_big.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test" style="width: 500px; height: 500px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_both.html b/browser/base/content/test/plugins/plugin_both.html new file mode 100644 index 000000000..2335366dc --- /dev/null +++ b/browser/base/content/test/plugins/plugin_both.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown"> +<embed id="test" style="width: 100px; height: 100px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_both2.html b/browser/base/content/test/plugins/plugin_both2.html new file mode 100644 index 000000000..ba605d6e8 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_both2.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head>
+<meta charset="utf-8">
+</head> +<body> +<embed id="test" style="width: 100px; height: 100px" type="application/x-test"> +<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_bug744745.html b/browser/base/content/test/plugins/plugin_bug744745.html new file mode 100644 index 000000000..d0691c9c0 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_bug744745.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body> +<style> +.x { + opacity: 0 !important; +} +</style> +<object id="test" class="x" type="application/x-test" width=200 height=200></object> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_bug749455.html b/browser/base/content/test/plugins/plugin_bug749455.html new file mode 100644 index 000000000..831dc82f7 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_bug749455.html @@ -0,0 +1,8 @@ +<!-- bug 749455 --> +<html> +<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> +</head> +<body> +<embed src="plugin_bug749455.html" type="application/x-test" width="100px" height="100px"></embed> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_bug787619.html b/browser/base/content/test/plugins/plugin_bug787619.html new file mode 100644 index 000000000..cb91116f0 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_bug787619.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body> + <a id="wrapper"> + <embed id="plugin" style="width: 200px; height: 200px" type="application/x-test"> + </a> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_bug797677.html b/browser/base/content/test/plugins/plugin_bug797677.html new file mode 100644 index 000000000..1545f3647 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_bug797677.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body><embed id="plugin" type="9000"></embed></body> +</html> diff --git a/browser/base/content/test/plugins/plugin_bug820497.html b/browser/base/content/test/plugins/plugin_bug820497.html new file mode 100644 index 000000000..4884e9dbe --- /dev/null +++ b/browser/base/content/test/plugins/plugin_bug820497.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body> +<object id="test" type="application/x-test" width=200 height=200></object> +<script> + function addSecondPlugin() { + var object = document.createElement("object"); + object.type = "application/x-second-test"; + object.width = 200; + object.height = 200; + object.id = "secondtest"; + document.body.appendChild(object); + } +</script> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_clickToPlayAllow.html b/browser/base/content/test/plugins/plugin_clickToPlayAllow.html new file mode 100644 index 000000000..3f5df1984 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_clickToPlayAllow.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head>
+<meta charset="utf-8">
+</head> +<body> +<embed id="test" style="width: 200px; height: 200px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_clickToPlayDeny.html b/browser/base/content/test/plugins/plugin_clickToPlayDeny.html new file mode 100644 index 000000000..3f5df1984 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_clickToPlayDeny.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head>
+<meta charset="utf-8">
+</head> +<body> +<embed id="test" style="width: 200px; height: 200px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_crashCommentAndURL.html b/browser/base/content/test/plugins/plugin_crashCommentAndURL.html new file mode 100644 index 000000000..711a19ed3 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_crashCommentAndURL.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <script type="text/javascript"> + function crash() { + var plugin = document.getElementById("plugin"); + var argStr = decodeURIComponent(window.location.search.substr(1)); + if (argStr) { + var args = JSON.parse(argStr); + for (var key in args) + plugin.setAttribute(key, args[key]); + } + try { + plugin.crash(); + } + catch (err) {} + } + </script> + </head> + <body onload="crash();"> + <embed id="plugin" type="application/x-test" + width="400" height="400" + drawmode="solid" color="FF00FFFF"> + </embed> + </body> +</html> diff --git a/browser/base/content/test/plugins/plugin_data_url.html b/browser/base/content/test/plugins/plugin_data_url.html new file mode 100644 index 000000000..77e101144 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_data_url.html @@ -0,0 +1,11 @@ +<html> +<body> + <a id="data-link-1" href='data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>'> + data: with one plugin + </a><br /> + <a id="data-link-2" href='data:text/html,<embed id="test1" style="width: 200px; height: 200px" type="application/x-test"/><embed id="test2" style="width: 200px; height: 200px" type="application/x-second-test"/>'> + data: with two plugins + </a><br /> + <object id="test" style="width: 200px; height: 200px" type="application/x-test"></object> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_hidden_to_visible.html b/browser/base/content/test/plugins/plugin_hidden_to_visible.html new file mode 100644 index 000000000..eeacc1874 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_hidden_to_visible.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <div id="container" style="display: none"> + <object id="test" type="application/x-test" style="width: 200px; height: 200px;"></object> + </div> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_iframe.html b/browser/base/content/test/plugins/plugin_iframe.html new file mode 100644 index 000000000..239c9a771 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_iframe.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<iframe id="frame" with="400" height="400" src="plugin_test.html"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_outsideScrollArea.html b/browser/base/content/test/plugins/plugin_outsideScrollArea.html new file mode 100644 index 000000000..c6ef50d5d --- /dev/null +++ b/browser/base/content/test/plugins/plugin_outsideScrollArea.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<style type="text/css"> +#container { + position: fixed; + top: 0; + bottom: 0; + width: 100%; + height: 100%; + background: blue; +} + +#test { + width: 400px; + height: 400px; + position: absolute; +} +</style> +</head> +<body> + <div id="container"></div> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_overlayed.html b/browser/base/content/test/plugins/plugin_overlayed.html new file mode 100644 index 000000000..11c127093 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_overlayed.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <style type="text/css"> + .absthing { + width: 400px; + height: 400px; + position: absolute; + left: 20px; + top: 20px; + } + #d1 { + z-index: 1; + } + #d2 { + z-index: 2; + background-color: rgba(0,0,255,0.5); + border: 1px solid red; + } + </style> +<body> + <div class="absthing" id="d1"> + <embed id="test" type="application/x-test"> + </div> + <div class="absthing" id="d2"> + <p>This is overlaying + </div> diff --git a/browser/base/content/test/plugins/plugin_positioned.html b/browser/base/content/test/plugins/plugin_positioned.html new file mode 100644 index 000000000..1bad7ee46 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_positioned.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <style type="text/css"> + #test { + position: absolute; + left: -1000px; + top: -1000px; + } + </style> +<body> + <embed id="test" type="application/x-test"> diff --git a/browser/base/content/test/plugins/plugin_small.html b/browser/base/content/test/plugins/plugin_small.html new file mode 100644 index 000000000..f37ee28c7 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_small.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test" style="width: 10px; height: 10px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_small_2.html b/browser/base/content/test/plugins/plugin_small_2.html new file mode 100644 index 000000000..ebc5ffe84 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_small_2.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test" style="width: 10px; height: 10px" type="application/x-second-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_syncRemoved.html b/browser/base/content/test/plugins/plugin_syncRemoved.html new file mode 100644 index 000000000..d97787056 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_syncRemoved.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<body> +<script type="text/javascript"> + // create an embed, insert it in the doc and immediately remove it + var embed = document.createElement('embed'); + embed.setAttribute("id", "test"); + embed.setAttribute("type", "application/x-test"); + embed.setAttribute("style", "width: 0px; height: 0px;"); + document.body.appendChild(embed); + window.getComputedStyle(embed, null).top; + document.body.remove(embed); +</script> diff --git a/browser/base/content/test/plugins/plugin_test.html b/browser/base/content/test/plugins/plugin_test.html new file mode 100644 index 000000000..d4b5b6ca7 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_test.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test" style="width: 200px; height: 200px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_test2.html b/browser/base/content/test/plugins/plugin_test2.html new file mode 100644 index 000000000..95614c930 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_test2.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test1" style="width: 200px; height: 200px" type="application/x-test"> +<embed id="test2" style="width: 200px; height: 200px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_test3.html b/browser/base/content/test/plugins/plugin_test3.html new file mode 100644 index 000000000..215c02326 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_test3.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test" style="width: 0px; height: 0px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_two_types.html b/browser/base/content/test/plugins/plugin_two_types.html new file mode 100644 index 000000000..2359d2ec1 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_two_types.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body> +<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/> +<embed id="secondtestA" style="width: 200px; height: 200px" type="application/x-second-test"/> +<embed id="secondtestB" style="width: 200px; height: 200px" type="application/x-second-test"/> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_unknown.html b/browser/base/content/test/plugins/plugin_unknown.html new file mode 100644 index 000000000..578f455cc --- /dev/null +++ b/browser/base/content/test/plugins/plugin_unknown.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_zoom.html b/browser/base/content/test/plugins/plugin_zoom.html new file mode 100644 index 000000000..f9e598658 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_zoom.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<!-- The odd width and height are here to trigger bug 972237. --> +<embed id="test" style="width: 99.789%; height: 99.123%" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/popupNotifications/.eslintrc.js b/browser/base/content/test/popupNotifications/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/popupNotifications/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/popupNotifications/browser.ini b/browser/base/content/test/popupNotifications/browser.ini new file mode 100644 index 000000000..83bb7c517 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser.ini @@ -0,0 +1,18 @@ +[DEFAULT] +support-files = + head.js + +[browser_displayURI.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_2.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_3.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_4.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_checkbox.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_reshow_in_background.js] +skip-if = (os == "linux" && (debug || asan)) diff --git a/browser/base/content/test/popupNotifications/browser_displayURI.js b/browser/base/content/test/popupNotifications/browser_displayURI.js new file mode 100644 index 000000000..48222be19 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_displayURI.js @@ -0,0 +1,28 @@ +/* + * Make sure that the origin is shown for ContentPermissionPrompt + * consumers e.g. geolocation. +*/ + +add_task(function* test_displayURI() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "https://test1.example.com/", + }, function*(browser) { + let popupShownPromise = new Promise((resolve, reject) => { + onPopupEvent("popupshown", function() { + resolve(this); + }); + }); + yield ContentTask.spawn(browser, null, function*() { + content.navigator.geolocation.getCurrentPosition(function (pos) { + // Do nothing + }); + }); + let panel = yield popupShownPromise; + let notification = panel.children[0]; + let body = document.getAnonymousElementByAttribute(notification, + "class", + "popup-notification-body"); + ok(body.innerHTML.includes("example.com"), "Check that at least the eTLD+1 is present in the markup"); + }); +}); diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification.js b/browser/base/content/test/popupNotifications/browser_popupNotification.js new file mode 100644 index 000000000..6be3e4205 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification.js @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// These are shared between test #4 to #5 +var wrongBrowserNotificationObject = new BasicNotification("wrongBrowser"); +var wrongBrowserNotification; + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + setup(); + goNext(); +} + +var tests = [ + { id: "Test#1", + run: function () { + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.mainActionClicked, "mainAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + { id: "Test#2", + run: function () { + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + { id: "Test#3", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // test opening a notification for a background browser + // Note: test 4 to 6 share a tab. + { id: "Test#4", + run: function* () { + let tab = gBrowser.addTab("about:blank"); + isnot(gBrowser.selectedTab, tab, "new tab isn't selected"); + wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(tab); + let promiseTopic = promiseTopicObserved("PopupNotifications-backgroundShow"); + wrongBrowserNotification = showNotification(wrongBrowserNotificationObject); + yield promiseTopic; + is(PopupNotifications.isPanelOpen, false, "panel isn't open"); + ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked"); + ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked"); + ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called"); + goNext(); + } + }, + // now select that browser and test to see that the notification appeared + { id: "Test#5", + run: function () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1]; + }, + onShown: function (popup) { + checkPopup(popup, wrongBrowserNotificationObject); + is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie"); + + // switch back to the old browser + gBrowser.selectedTab = this.oldSelectedTab; + }, + onHidden: function (popup) { + // actually remove the notification to prevent it from reappearing + ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch"); + wrongBrowserNotification.remove(); + ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered"); + wrongBrowserNotification = null; + } + }, + // test that the removed notification isn't shown on browser re-select + { id: "Test#6", + run: function* () { + let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing"); + gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1]; + yield promiseTopic; + is(PopupNotifications.isPanelOpen, false, "panel isn't open"); + gBrowser.removeTab(gBrowser.selectedTab); + goNext(); + } + }, + // Test that two notifications with the same ID result in a single displayed + // notification. + { id: "Test#7", + run: function () { + this.notifyObj = new BasicNotification(this.id); + // Show the same notification twice + this.notification1 = showNotification(this.notifyObj); + this.notification2 = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + this.notification2.remove(); + }, + onHidden: function (popup) { + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that two notifications with different IDs are displayed + { id: "Test#8", + run: function () { + this.testNotif1 = new BasicNotification(this.id); + this.testNotif1.message += " 1"; + showNotification(this.testNotif1); + this.testNotif2 = new BasicNotification(this.id); + this.testNotif2.message += " 2"; + this.testNotif2.id += "-2"; + showNotification(this.testNotif2); + }, + onShown: function (popup) { + is(popup.childNodes.length, 2, "two notifications are shown"); + // Trigger the main command for the first notification, and the secondary + // for the second. Need to do mainCommand first since the secondaryCommand + // triggering is async. + triggerMainCommand(popup); + is(popup.childNodes.length, 1, "only one notification left"); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.testNotif1.mainActionClicked, "main action #1 was clicked"); + ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked"); + ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called"); + + ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked"); + ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked"); + ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called"); + } + }, + // Test notification without mainAction + { id: "Test#9", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.mainAction = null; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notification.remove(); + } + }, + // Test two notifications with different anchors + { id: "Test#10", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.firstNotification = showNotification(this.notifyObj); + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "-2"; + this.notifyObj2.anchorID = "addons-notification-icon"; + // Second showNotification() overrides the first + this.secondNotification = showNotification(this.notifyObj2); + }, + onShown: function (popup) { + // This also checks that only one element is shown. + checkPopup(popup, this.notifyObj2); + is(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor shouldn't be visible"); + dismissNotification(popup); + }, + onHidden: function (popup) { + // Remove the notifications + this.firstNotification.remove(); + this.secondNotification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered"); + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js new file mode 100644 index 000000000..d77098895 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js @@ -0,0 +1,266 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + setup(); + goNext(); +} + +var tests = [ + // Test optional params + { id: "Test#1", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.secondaryActions = undefined; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that icons appear + { id: "Test#2", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.id = "geolocation"; + this.notifyObj.anchorID = "geo-notification-icon"; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + dismissNotification(popup); + }, + onHidden: function (popup) { + let icon = document.getElementById("geo-notification-icon"); + isnot(icon.boxObject.width, 0, + "geo anchor should be visible after dismissal"); + this.notification.remove(); + is(icon.boxObject.width, 0, + "geo anchor should not be visible after removal"); + } + }, + + // Test that persistence allows the notification to persist across reloads + { id: "Test#3", + run: function* () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.addOptions({ + persistence: 2 + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + this.complete = false; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/") + // Next load will remove the notification + this.complete = true; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after 3 page loads"); + ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered"); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + // Test that a timeout allows the notification to persist across reloads + { id: "Test#4", + run: function* () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + this.notifyObj = new BasicNotification(this.id); + // Set a timeout of 10 minutes that should never be hit + this.notifyObj.addOptions({ + timeout: Date.now() + 600000 + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + this.complete = false; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + // Next load will hide the notification + this.notification.options.timeout = Date.now() - 1; + this.complete = true; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after the timeout was passed"); + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + // Test that setting persistWhileVisible allows a visible notification to + // persist across location changes + { id: "Test#5", + run: function* () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.addOptions({ + persistWhileVisible: true + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + this.complete = false; + + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + // Notification should persist across location changes + this.complete = true; + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after it was dismissed"); + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + + // Test that nested icon nodes correctly activate popups + { id: "Test#6", + run: function() { + // Add a temporary box as the anchor with a button + this.box = document.createElement("box"); + PopupNotifications.iconBox.appendChild(this.box); + + let button = document.createElement("button"); + button.setAttribute("label", "Please click me!"); + this.box.appendChild(button); + + // The notification should open up on the box + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.anchorID = this.box.id = "nested-box"; + this.notifyObj.addOptions({dismissed: true}); + this.notification = showNotification(this.notifyObj); + + // This test places a normal button in the notification area, which has + // standard GTK styling and dimensions. Due to the clip-path, this button + // gets clipped off, which makes it necessary to synthesize the mouse click + // a little bit downward. To be safe, I adjusted the x-offset with the same + // amount. + EventUtils.synthesizeMouse(button, 4, 4, {}); + }, + onShown: function(popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function(popup) { + this.notification.remove(); + this.box.parentNode.removeChild(this.box); + } + }, + // Test that popupnotifications without popups have anchor icons shown + { id: "Test#7", + run: function* () { + let notifyObj = new BasicNotification(this.id); + notifyObj.anchorID = "geo-notification-icon"; + notifyObj.addOptions({neverShow: true}); + let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing"); + showNotification(notifyObj); + yield promiseTopic; + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + goNext(); + } + }, + // Test notification "Not Now" menu item + { id: "Test#8", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 1); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification close button + { id: "Test#9", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + EventUtils.synthesizeMouseAtCenter(notification.closebutton, {}); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification when chrome is hidden + { id: "Test#10", + run: function () { + window.locationbar.visible = false; + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab"); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + window.locationbar.visible = true; + } + }, + // Test that dismissed popupnotifications can be opened on about:blank + // (where the rest of the identity block is disabled) + { id: "Test#11", + run: function() { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.anchorID = "geo-notification-icon"; + this.notifyObj.addOptions({dismissed: true}); + this.notification = showNotification(this.notifyObj); + + EventUtils.synthesizeMouse(document.getElementById("geo-notification-icon"), 0, 0, {}); + }, + onShown: function(popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function(popup) { + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_3.js b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js new file mode 100644 index 000000000..33ec3f714 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js @@ -0,0 +1,305 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + setup(); + goNext(); +} + +var tests = [ + // Test notification is removed when dismissed if removeOnDismissal is true + { id: "Test#1", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.addOptions({ + removeOnDismissal: true + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test multiple notification icons are shown + { id: "Test#2", + run: function () { + this.notifyObj1 = new BasicNotification(this.id); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notification1 = showNotification(this.notifyObj1); + + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notification2 = showNotification(this.notifyObj2); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj2); + + // check notifyObj1 anchor icon is showing + isnot(document.getElementById("default-notification-icon").boxObject.width, 0, + "default anchor should be visible"); + // check notifyObj2 anchor icon is showing + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notification1.remove(); + ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered"); + + this.notification2.remove(); + ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that multiple notification icons are removed when switching tabs + { id: "Test#3", + run: function () { + // show the notification on old tab. + this.notifyObjOld = new BasicNotification(this.id); + this.notifyObjOld.anchorID = "default-notification-icon"; + this.notificationOld = showNotification(this.notifyObjOld); + + // switch tab + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + // show the notification on new tab. + this.notifyObjNew = new BasicNotification(this.id); + this.notifyObjNew.anchorID = "geo-notification-icon"; + this.notificationNew = showNotification(this.notifyObjNew); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObjNew); + + // check notifyObjOld anchor icon is removed + is(document.getElementById("default-notification-icon").boxObject.width, 0, + "default anchor shouldn't be visible"); + // check notifyObjNew anchor icon is showing + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notificationNew.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + + gBrowser.selectedTab = this.oldSelectedTab; + this.notificationOld.remove(); + } + }, + // test security delay - too early + { id: "Test#4", + run: function () { + // Set the security delay to 100s + PopupNotifications.buttonDelay = 100000; + + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + + // Wait to see if the main command worked + executeSoon(function delayedDismissal() { + dismissNotification(popup); + }); + + }, + onHidden: function (popup) { + ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon"); + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered"); + } + }, + // test security delay - after delay + { id: "Test#5", + run: function () { + // Set the security delay to 10ms + PopupNotifications.buttonDelay = 10; + + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + + // Wait until after the delay to trigger the main action + setTimeout(function delayedDismissal() { + triggerMainCommand(popup); + }, 500); + + }, + onHidden: function (popup) { + ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered"); + PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL; + } + }, + // reload removes notification + { id: "Test#6", + run: function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + let notifyObj = new BasicNotification(this.id); + notifyObj.options.eventCallback = function (eventName) { + if (eventName == "removed") { + ok(true, "Notification removed in background tab after reloading"); + goNext(); + } + }; + showNotification(notifyObj); + executeSoon(function () { + gBrowser.selectedBrowser.reload(); + }); + } + }, + // location change in background tab removes notification + { id: "Test#7", + run: function* () { + let oldSelectedTab = gBrowser.selectedTab; + let newTab = gBrowser.addTab("about:blank"); + gBrowser.selectedTab = newTab; + + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + gBrowser.selectedTab = oldSelectedTab; + let browser = gBrowser.getBrowserForTab(newTab); + + let notifyObj = new BasicNotification(this.id); + notifyObj.browser = browser; + notifyObj.options.eventCallback = function (eventName) { + if (eventName == "removed") { + ok(true, "Notification removed in background tab after reloading"); + executeSoon(function () { + gBrowser.removeTab(newTab); + goNext(); + }); + } + }; + showNotification(notifyObj); + executeSoon(function () { + browser.reload(); + }); + } + }, + // Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab + { id: "Test#8", + run: function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + let originalTab = gBrowser.selectedTab; + let bgTab = gBrowser.addTab("about:blank"); + gBrowser.selectedTab = bgTab; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + let anchor = document.createElement("box"); + anchor.id = "test26-anchor"; + anchor.className = "notification-anchor-icon"; + PopupNotifications.iconBox.appendChild(anchor); + + gBrowser.selectedTab = originalTab; + + let fgNotifyObj = new BasicNotification(this.id); + fgNotifyObj.anchorID = anchor.id; + fgNotifyObj.options.dismissed = true; + let fgNotification = showNotification(fgNotifyObj); + + let bgNotifyObj = new BasicNotification(this.id); + bgNotifyObj.anchorID = anchor.id; + bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab); + // show the notification in the background tab ... + let bgNotification = showNotification(bgNotifyObj); + // ... and re-show it + bgNotification = showNotification(bgNotifyObj); + + ok(fgNotification.id, "notification has id"); + is(fgNotification.id, bgNotification.id, "notification ids are the same"); + is(anchor.getAttribute("showing"), "true", "anchor still showing"); + + fgNotification.remove(); + gBrowser.removeTab(bgTab); + goNext(); + } + }, + // location change in an embedded frame should not remove a notification + { id: "Test#9", + run: function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html;charset=utf8,<iframe%20id='iframe'%20src='http://example.com/'>"); + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.eventCallback = function (eventName) { + if (eventName == "removed") { + ok(false, "Notification removed from browser when subframe navigated"); + } + }; + showNotification(this.notifyObj); + }, + onShown: function (popup) { + let self = this; + let progressListener = { + onLocationChange: function onLocationChange() { + gBrowser.removeProgressListener(progressListener); + + executeSoon(() => { + let notification = PopupNotifications.getNotification(self.notifyObj.id, + self.notifyObj.browser); + ok(notification != null, "Notification remained when subframe navigated"); + self.notifyObj.options.eventCallback = undefined; + + notification.remove(); + }); + }, + }; + + info("Adding progress listener and performing navigation"); + gBrowser.addProgressListener(progressListener); + ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + content.document.getElementById("iframe") + .setAttribute("src", "http://example.org/"); + }); + }, + onHidden: function () {} + }, + // Popup Notifications should catch exceptions from callbacks + { id: "Test#10", + run: function () { + this.testNotif1 = new BasicNotification(this.id); + this.testNotif1.message += " 1"; + this.notification1 = showNotification(this.testNotif1); + this.testNotif1.options.eventCallback = function (eventName) { + info("notifyObj1.options.eventCallback: " + eventName); + if (eventName == "dismissed") { + throw new Error("Oops 1!"); + } + }; + + this.testNotif2 = new BasicNotification(this.id); + this.testNotif2.message += " 2"; + this.testNotif2.id += "-2"; + this.testNotif2.options.eventCallback = function (eventName) { + info("notifyObj2.options.eventCallback: " + eventName); + if (eventName == "dismissed") { + throw new Error("Oops 2!"); + } + }; + this.notification2 = showNotification(this.testNotif2); + }, + onShown: function (popup) { + is(popup.childNodes.length, 2, "two notifications are shown"); + dismissNotification(popup); + }, + onHidden: function () { + this.notification1.remove(); + this.notification2.remove(); + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js new file mode 100644 index 000000000..750ad82fd --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js @@ -0,0 +1,294 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + setup(); + goNext(); +} + +var tests = [ + // Popup Notifications main actions should catch exceptions from callbacks + { id: "Test#1", + run: function () { + this.testNotif = new ErrorNotification(); + showNotification(this.testNotif); + }, + onShown: function (popup) { + checkPopup(popup, this.testNotif); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + ok(this.testNotif.mainActionClicked, "main action has been triggered"); + } + }, + // Popup Notifications secondary actions should catch exceptions from callbacks + { id: "Test#2", + run: function () { + this.testNotif = new ErrorNotification(); + showNotification(this.testNotif); + }, + onShown: function (popup) { + checkPopup(popup, this.testNotif); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered"); + } + }, + // Existing popup notification shouldn't disappear when adding a dismissed notification + { id: "Test#3", + run: function () { + this.notifyObj1 = new BasicNotification(this.id); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notification1 = showNotification(this.notifyObj1); + }, + onShown: function (popup) { + // Now show a dismissed notification, and check that it doesn't clobber + // the showing one. + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notifyObj2.options.dismissed = true; + this.notification2 = showNotification(this.notifyObj2); + + checkPopup(popup, this.notifyObj1); + + // check that both anchor icons are showing + is(document.getElementById("default-notification-icon").getAttribute("showing"), "true", + "notification1 anchor should be visible"); + is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true", + "notification2 anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: function(popup) { + this.notification1.remove(); + this.notification2.remove(); + } + }, + // Showing should be able to modify the popup data + { id: "Test#4", + run: function() { + this.notifyObj = new BasicNotification(this.id); + let normalCallback = this.notifyObj.options.eventCallback; + this.notifyObj.options.eventCallback = function (eventName) { + if (eventName == "showing") { + this.mainAction.label = "Alternate Label"; + } + normalCallback.call(this, eventName); + }; + showNotification(this.notifyObj); + }, + onShown: function(popup) { + // checkPopup checks for the matching label. Note that this assumes that + // this.notifyObj.mainAction is the same as notification.mainAction, + // which could be a problem if we ever decided to deep-copy. + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function() { } + }, + // Moving a tab to a new window should remove non-swappable notifications. + { id: "Test#5", + run: function() { + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + let notifyObj = new BasicNotification(this.id); + showNotification(notifyObj); + let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + whenDelayedStartupFinished(win, function() { + let anchor = win.document.getElementById("default-notification-icon"); + win.PopupNotifications._reshowNotifications(anchor); + ok(win.PopupNotifications.panel.childNodes.length == 0, + "no notification displayed in new window"); + ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered"); + ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered"); + win.close(); + goNext(); + }); + } + }, + // Moving a tab to a new window should preserve swappable notifications. + { id: "Test#6", + run: function* () { + let originalBrowser = gBrowser.selectedBrowser; + let originalWindow = window; + + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + let notifyObj = new BasicNotification(this.id); + let originalCallback = notifyObj.options.eventCallback; + notifyObj.options.eventCallback = function (eventName) { + originalCallback(eventName); + return eventName == "swapping"; + }; + + let notification = showNotification(notifyObj); + let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + yield whenDelayedStartupFinished(win); + + yield new Promise(resolve => { + let originalCallback = notification.options.eventCallback; + notification.options.eventCallback = function (eventName) { + originalCallback(eventName); + if (eventName == "shown") { + resolve(); + } + }; + info("Showing the notification again"); + notification.reshow(); + }); + + checkPopup(win.PopupNotifications.panel, notifyObj); + ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered"); + yield BrowserTestUtils.closeWindow(win); + + // These are the same checks that PopupNotifications.jsm makes before it + // allows a notification to open. Do not go to the next test until we are + // sure that its attempt to display a notification will not fail. + yield BrowserTestUtils.waitForCondition(() => originalBrowser.docShellIsActive, + "The browser should be active"); + let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + yield BrowserTestUtils.waitForCondition(() => fm.activeWindow == originalWindow, + "The window should be active") + + goNext(); + } + }, + // the hideNotNow option + { id: "Test#7", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.hideNotNow = true; + this.notifyObj.mainAction.dismiss = true; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + // checkPopup verifies that the Not Now item is hidden, and that no separator is added. + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + this.notification.remove(); + } + }, + // the main action callback can keep the notification. + { id: "Test#8", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.mainAction.dismiss = true; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered"); + ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered"); + this.notification.remove(); + } + }, + // a secondary action callback can keep the notification. + { id: "Test#9", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.secondaryActions[0].dismiss = true; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered"); + ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered"); + this.notification.remove(); + } + }, + // returning true in the showing callback should dismiss the notification. + { id: "Test#10", + run: function() { + let notifyObj = new BasicNotification(this.id); + let originalCallback = notifyObj.options.eventCallback; + notifyObj.options.eventCallback = function (eventName) { + originalCallback(eventName); + return eventName == "showing"; + }; + + let notification = showNotification(notifyObj); + ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered"); + ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered"); + notification.remove(); + goNext(); + } + }, + // panel updates should fire the showing and shown callbacks again. + { id: "Test#11", + run: function() { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + + this.notifyObj.showingCallbackTriggered = false; + this.notifyObj.shownCallbackTriggered = false; + + // Force an update of the panel. This is typically called + // automatically when receiving 'activate' or 'TabSelect' events, + // but from a setTimeout, which is inconvenient for the test. + PopupNotifications._update(); + + checkPopup(popup, this.notifyObj); + + this.notification.remove(); + }, + onHidden: function() { } + }, + // A first dismissed notification shouldn't stop _update from showing a second notification + { id: "Test#12", + run: function () { + this.notifyObj1 = new BasicNotification(this.id); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notifyObj1.options.dismissed = true; + this.notification1 = showNotification(this.notifyObj1); + + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notifyObj2.options.dismissed = true; + this.notification2 = showNotification(this.notifyObj2); + + this.notification2.dismissed = false; + PopupNotifications._update(); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj2); + this.notification1.remove(); + this.notification2.remove(); + }, + onHidden: function(popup) { } + }, + // The anchor icon should be shown for notifications in background windows. + { id: "Test#13", + run: function() { + let notifyObj = new BasicNotification(this.id); + notifyObj.options.dismissed = true; + let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank")); + whenDelayedStartupFinished(win, function() { + showNotification(notifyObj); + let anchor = document.getElementById("default-notification-icon"); + is(anchor.getAttribute("showing"), "true", "the anchor is shown"); + win.close(); + goNext(); + }); + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js new file mode 100644 index 000000000..bcc51fcd7 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js @@ -0,0 +1,211 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + setup(); + goNext(); +} + +function checkCheckbox(checkbox, label, checked=false, hidden=false) { + is(checkbox.label, label, "Checkbox should have the correct label"); + is(checkbox.hidden, hidden, "Checkbox should be shown"); + is(checkbox.checked, checked, "Checkbox should be checked by default"); +} + +function checkMainAction(notification, disabled=false) { + let mainAction = notification.button; + let warningLabel = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-warning"); + is(warningLabel.hidden, !disabled, "Warning label should be shown"); + is(mainAction.disabled, disabled, "MainAction should be disabled"); +} + +function promiseElementVisible(element) { + // HTMLElement.offsetParent is null when the element is not visisble + // (or if the element has |position: fixed|). See: + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent + return BrowserTestUtils.waitForCondition(() => element.offsetParent !== null, + "Waiting for element to be visible"); +} + +var gNotification; + +var tests = [ + // Test that passing the checkbox field shows the checkbox. + { id: "show_checkbox", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + checkCheckbox(notification.checkbox, "This is a checkbox"); + triggerMainCommand(popup); + }, + onHidden: function () { } + }, + + // Test checkbox being checked by default + { id: "checkbox_checked", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "Check this", + checked: true, + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + checkCheckbox(notification.checkbox, "Check this", true); + triggerMainCommand(popup); + }, + onHidden: function () { } + }, + + // Test checkbox passing the checkbox state on mainAction + { id: "checkbox_passCheckboxChecked_mainAction", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.mainAction.callback = ({checkboxChecked}) => this.mainActionChecked = checkboxChecked; + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox"); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", true); + triggerMainCommand(popup); + }, + onHidden: function () { + is(this.mainActionChecked, true, "mainAction callback is passed the correct checkbox value"); + } + }, + + // Test checkbox passing the checkbox state on secondaryAction + { id: "checkbox_passCheckboxChecked_secondaryAction", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.secondaryActions = [{ + label: "Test Secondary", + accessKey: "T", + callback: ({checkboxChecked}) => this.secondaryActionChecked = checkboxChecked, + }]; + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox"); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", true); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function () { + is(this.secondaryActionChecked, true, "secondaryAction callback is passed the correct checkbox value"); + } + }, + + // Test checkbox preserving its state through re-opening the doorhanger + { id: "checkbox_reopen", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + checkedState: { + disableMainAction: true, + warningLabel: "Testing disable", + }, + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox"); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + dismissNotification(popup); + }, + onHidden: function* (popup) { + let icon = document.getElementById("default-notification-icon"); + let shown = waitForNotificationPanel(); + EventUtils.synthesizeMouseAtCenter(icon, {}); + yield shown; + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox", true); + checkMainAction(notification, true); + gNotification.remove(); + } + }, +]; + +// Test checkbox disabling the main action in different combinations +["checkedState", "uncheckedState"].forEach(function (state) { + [true, false].forEach(function (checked) { + tests.push( + { id: `checkbox_disableMainAction_${state}_${checked ? 'checked' : 'unchecked'}`, + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + checked: checked, + [state]: { + disableMainAction: true, + warningLabel: "Testing disable", + }, + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + let disabled = (state === "checkedState" && checked) || + (state === "uncheckedState" && !checked); + + checkCheckbox(checkbox, "This is a checkbox", checked); + checkMainAction(notification, disabled); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", !checked); + checkMainAction(notification, !disabled); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", checked); + checkMainAction(notification, disabled); + + // Unblock the main command if it's currently disabled. + if (disabled) { + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + } + triggerMainCommand(popup); + }, + onHidden: function () { } + } + ); + }); +}); + diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js new file mode 100644 index 000000000..0f5b57ced --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + setup(); +} + +var tests = [ + // Test that for persistent notifications, + // the secondary action is triggered by pressing the escape key. + { id: "Test#1", + run() { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.persistent = true; + showNotification(this.notifyObj); + }, + onShown(popup) { + checkPopup(popup, this.notifyObj); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + }, + onHidden(popup) { + ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked"); + ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that for non-persistent notifications, the escape key dismisses the notification. + { id: "Test#2", + *run() { + yield waitForWindowReadyForPopupNotifications(window); + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown(popup) { + checkPopup(popup, this.notifyObj); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + }, + onHidden(popup) { + ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked"); + ok(!this.notifyObj.secondaryActionClicked, "secondaryAction was not clicked"); + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered"); + this.notification.remove(); + } + }, + // Test that the space key on an anchor element focuses an active notification + { id: "Test#3", + *run() { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.anchorID = "geo-notification-icon"; + this.notifyObj.addOptions({ + persistent: true + }); + this.notification = showNotification(this.notifyObj); + }, + *onShown(popup) { + checkPopup(popup, this.notifyObj); + let anchor = document.getElementById(this.notifyObj.anchorID); + anchor.focus(); + is(document.activeElement, anchor); + EventUtils.synthesizeKey(" ", {}); + is(document.activeElement, popup.childNodes[0].button); + this.notification.remove(); + }, + onHidden(popup) { } + }, +]; diff --git a/browser/base/content/test/popupNotifications/browser_reshow_in_background.js b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js new file mode 100644 index 000000000..6f415f62e --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js @@ -0,0 +1,52 @@ +"use strict"; + +/** + * Tests that when PopupNotifications for background tabs are reshown, they + * don't show up in the foreground tab, but only in the background tab that + * they belong to. + */ +add_task(function* test_background_notifications_dont_reshow_in_foreground() { + // Our initial tab will be A. Let's open two more tabs B and C, but keep + // A selected. Then, we'll trigger a PopupNotification in C, and then make + // it reshow. + let tabB = gBrowser.addTab("about:blank"); + let tabC = gBrowser.addTab("about:blank"); + + let seenEvents = []; + + let options = { + dismissed: false, + eventCallback(popupEvent) { + seenEvents.push(popupEvent); + }, + }; + + let notification = + PopupNotifications.show(tabC.linkedBrowser, "test-notification", + "", "plugins-notification-icon", + null, null, options); + Assert.deepEqual(seenEvents, [], "Should have seen no events yet."); + + yield BrowserTestUtils.switchTab(gBrowser, tabB); + Assert.deepEqual(seenEvents, [], "Should have seen no events yet."); + + notification.reshow(); + Assert.deepEqual(seenEvents, [], "Should have seen no events yet."); + + let panelShown = + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); + yield BrowserTestUtils.switchTab(gBrowser, tabC); + yield panelShown; + + Assert.equal(seenEvents.length, 2, "Should have seen two events."); + Assert.equal(seenEvents[0], "showing", "Should have said popup was showing."); + Assert.equal(seenEvents[1], "shown", "Should have said popup was shown."); + + let panelHidden = + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden"); + PopupNotifications.remove(notification); + yield panelHidden; + + yield BrowserTestUtils.removeTab(tabB); + yield BrowserTestUtils.removeTab(tabC); +}); diff --git a/browser/base/content/test/popupNotifications/head.js b/browser/base/content/test/popupNotifications/head.js new file mode 100644 index 000000000..4a803d6af --- /dev/null +++ b/browser/base/content/test/popupNotifications/head.js @@ -0,0 +1,303 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); + +function whenDelayedStartupFinished(aWindow, aCallback) { + return new Promise(resolve => { + info("Waiting for delayed startup to finish"); + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + if (aCallback) { + executeSoon(aCallback); + } + resolve(); + } + }, "browser-delayed-startup-finished", false); + }); +} + +/** + * Allows waiting for an observer notification once. + * + * @param topic + * Notification topic to observe. + * + * @return {Promise} + * @resolves The array [subject, data] from the observed notification. + * @rejects Never. + */ +function promiseTopicObserved(topic) +{ + let deferred = Promise.defer(); + info("Waiting for observer topic " + topic); + Services.obs.addObserver(function PTO_observe(subject, topic, data) { + Services.obs.removeObserver(PTO_observe, topic); + deferred.resolve([subject, data]); + }, topic, false); + return deferred.promise; +} + + +/** + * 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) +{ + let browser = tab.linkedBrowser; + + if (url) { + browser.loadURI(url); + } + + return BrowserTestUtils.browserLoaded(browser, false, url); +} + +const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay"); + +function setup() { + // Disable transitions as they slow the test down and we want to click the + // mouse buttons in a predictable location. + + registerCleanupFunction(() => { + PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL; + }); +} + +function goNext() { + executeSoon(() => executeSoon(Task.async(runNextTest))); +} + +function* runNextTest() { + if (tests.length == 0) { + executeSoon(finish); + return; + } + + let nextTest = tests.shift(); + if (nextTest.onShown) { + let shownState = false; + onPopupEvent("popupshowing", function () { + info("[" + nextTest.id + "] popup showing"); + }); + onPopupEvent("popupshown", function () { + shownState = true; + info("[" + nextTest.id + "] popup shown"); + Task.spawn(() => nextTest.onShown(this)) + .then(undefined, ex => Assert.ok(false, "onShown failed: " + ex)); + }); + onPopupEvent("popuphidden", function () { + info("[" + nextTest.id + "] popup hidden"); + Task.spawn(() => nextTest.onHidden(this)) + .then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex)); + }, () => shownState); + info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen); + } + + info("[" + nextTest.id + "] running test"); + yield nextTest.run(); +} + +function showNotification(notifyObj) { + info("Showing notification " + notifyObj.id); + return PopupNotifications.show(notifyObj.browser, + notifyObj.id, + notifyObj.message, + notifyObj.anchorID, + notifyObj.mainAction, + notifyObj.secondaryActions, + notifyObj.options); +} + +function dismissNotification(popup) { + info("Dismissing notification " + popup.childNodes[0].id); + executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {})); +} + +function BasicNotification(testId) { + this.browser = gBrowser.selectedBrowser; + this.id = "test-notification-" + testId; + this.message = "This is popup notification for " + testId; + this.anchorID = null; + this.mainAction = { + label: "Main Action", + accessKey: "M", + callback: () => this.mainActionClicked = true + }; + this.secondaryActions = [ + { + label: "Secondary Action", + accessKey: "S", + callback: () => this.secondaryActionClicked = true + } + ]; + this.options = { + eventCallback: eventName => { + switch (eventName) { + case "dismissed": + this.dismissalCallbackTriggered = true; + break; + case "showing": + this.showingCallbackTriggered = true; + break; + case "shown": + this.shownCallbackTriggered = true; + break; + case "removed": + this.removedCallbackTriggered = true; + break; + case "swapping": + this.swappingCallbackTriggered = true; + break; + } + } + }; +} + +BasicNotification.prototype.addOptions = function(options) { + for (let [name, value] of Object.entries(options)) + this.options[name] = value; +}; + +function ErrorNotification() { + this.mainAction.callback = () => { + this.mainActionClicked = true; + throw new Error("Oops!"); + }; + this.secondaryActions[0].callback = () => { + this.secondaryActionClicked = true; + throw new Error("Oops!"); + }; +} + +ErrorNotification.prototype = new BasicNotification(); +ErrorNotification.prototype.constructor = ErrorNotification; + +function checkPopup(popup, notifyObj) { + info("Checking notification " + notifyObj.id); + + ok(notifyObj.showingCallbackTriggered, "showing callback was triggered"); + ok(notifyObj.shownCallbackTriggered, "shown callback was triggered"); + + let notifications = popup.childNodes; + is(notifications.length, 1, "one notification displayed"); + let notification = notifications[0]; + if (!notification) + return; + let icon = document.getAnonymousElementByAttribute(notification, "class", + "popup-notification-icon"); + if (notifyObj.id == "geolocation") { + isnot(icon.boxObject.width, 0, "icon for geo displayed"); + ok(popup.anchorNode.classList.contains("notification-anchor-icon"), + "notification anchored to icon"); + } + is(notification.getAttribute("label"), notifyObj.message, "message matches"); + is(notification.id, notifyObj.id + "-notification", "id matches"); + if (notifyObj.mainAction) { + is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label, + "main action label matches"); + is(notification.getAttribute("buttonaccesskey"), + notifyObj.mainAction.accessKey, "main action accesskey matches"); + } + let actualSecondaryActions = + Array.filter(notification.childNodes, child => child.nodeName == "menuitem"); + let secondaryActions = notifyObj.secondaryActions || []; + let actualSecondaryActionsCount = actualSecondaryActions.length; + if (notifyObj.options.hideNotNow) { + is(notification.getAttribute("hidenotnow"), "true", "'Not Now' item hidden"); + if (secondaryActions.length) + is(notification.lastChild.tagName, "menuitem", "no menuseparator"); + } + else if (secondaryActions.length) { + is(notification.lastChild.tagName, "menuseparator", "menuseparator exists"); + } + is(actualSecondaryActionsCount, secondaryActions.length, + actualSecondaryActions.length + " secondary actions"); + secondaryActions.forEach(function (a, i) { + is(actualSecondaryActions[i].getAttribute("label"), a.label, + "label for secondary action " + i + " matches"); + is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, + "accessKey for secondary action " + i + " matches"); + }); +} + +XPCOMUtils.defineLazyGetter(this, "gActiveListeners", () => { + let listeners = new Map(); + registerCleanupFunction(() => { + for (let [listener, eventName] of listeners) { + PopupNotifications.panel.removeEventListener(eventName, listener, false); + } + }); + return listeners; +}); + +function onPopupEvent(eventName, callback, condition) { + let listener = event => { + if (event.target != PopupNotifications.panel || + (condition && !condition())) + return; + PopupNotifications.panel.removeEventListener(eventName, listener, false); + gActiveListeners.delete(listener); + executeSoon(() => callback.call(PopupNotifications.panel)); + } + gActiveListeners.set(listener, eventName); + PopupNotifications.panel.addEventListener(eventName, listener, false); +} + +function waitForNotificationPanel() { + return new Promise(resolve => { + onPopupEvent("popupshown", function() { + resolve(this); + }); + }); +} + +function triggerMainCommand(popup) { + let notifications = popup.childNodes; + ok(notifications.length > 0, "at least one notification displayed"); + let notification = notifications[0]; + info("Triggering main command for notification " + notification.id); + // 20, 10 so that the inner button is hit + EventUtils.synthesizeMouse(notification.button, 20, 10, {}); +} + +function triggerSecondaryCommand(popup, index) { + let notifications = popup.childNodes; + ok(notifications.length > 0, "at least one notification displayed"); + let notification = notifications[0]; + info("Triggering secondary command for notification " + notification.id); + // Cancel the arrow panel slide-in transition (bug 767133) such that + // it won't interfere with us interacting with the dropdown. + document.getAnonymousNodes(popup)[0].style.transition = "none"; + + notification.button.focus(); + + popup.addEventListener("popupshown", function handle() { + popup.removeEventListener("popupshown", handle, false); + info("Command popup open for notification " + notification.id); + // Press down until the desired command is selected + for (let i = 0; i <= index; i++) { + EventUtils.synthesizeKey("VK_DOWN", {}); + } + // Activate + EventUtils.synthesizeKey("VK_RETURN", {}); + }, false); + + // One down event to open the popup + info("Open the popup to trigger secondary command for notification " + notification.id); + EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.includes("Mac") }); +} diff --git a/browser/base/content/test/popups/browser.ini b/browser/base/content/test/popups/browser.ini new file mode 100644 index 000000000..46a32783b --- /dev/null +++ b/browser/base/content/test/popups/browser.ini @@ -0,0 +1,4 @@ +[browser_popupUI.js] +[browser_popup_blocker.js] +support-files = popup_blocker.html +skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures diff --git a/browser/base/content/test/popups/browser_popupUI.js b/browser/base/content/test/popups/browser_popupUI.js new file mode 100644 index 000000000..7c6805f60 --- /dev/null +++ b/browser/base/content/test/popups/browser_popupUI.js @@ -0,0 +1,37 @@ +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({ set: [[ "dom.disable_open_during_load", false ]] }); + + let popupOpened = BrowserTestUtils.waitForNewWindow(true, "about:blank"); + BrowserTestUtils.openNewForegroundTab(gBrowser, + "data:text/html,<html><script>popup=open('about:blank','','width=300,height=200')</script>" + ); + popupOpened.then((win) => testPopupUI(win)); +} + +function testPopupUI(win) { + var doc = win.document; + + ok(win.gURLBar, "location bar exists in the popup"); + isnot(win.gURLBar.clientWidth, 0, "location bar is visible in the popup"); + ok(win.gURLBar.readOnly, "location bar is read-only in the popup"); + isnot(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true", + "'open location' command is not disabled in the popup"); + + let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid", + "historydropmarker"); + is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup"); + + EventUtils.synthesizeKey("t", { accelKey: true }, win); + is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup"); + is(gBrowser.browsers.length, 3, "Accel+T opened a new tab in the parent window"); + gBrowser.removeCurrentTab(); + + EventUtils.synthesizeKey("w", { accelKey: true }, win); + ok(win.closed, "Accel+W closes the popup"); + + if (!win.closed) + win.close(); + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/browser/base/content/test/popups/browser_popup_blocker.js b/browser/base/content/test/popups/browser_popup_blocker.js new file mode 100644 index 000000000..8cadfea57 --- /dev/null +++ b/browser/base/content/test/popups/browser_popup_blocker.js @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const baseURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com"); + +function clearAllPermissionsByPrefix(aPrefix) { + let perms = Services.perms.enumerator; + while (perms.hasMoreElements()) { + let perm = perms.getNext(); + if (perm.type.startsWith(aPrefix)) { + Services.perms.removePermission(perm); + } + } +} + +add_task(function* test_opening_blocked_popups() { + // Enable the popup blocker. + yield SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]}); + + // Open the test page. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, baseURL + "popup_blocker.html"); + + // Wait for the popup-blocked notification. + let notification; + yield BrowserTestUtils.waitForCondition(() => + notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked")); + + // Show the menu. + let popupShown = BrowserTestUtils.waitForEvent(window, "popupshown"); + let popupFilled = BrowserTestUtils.waitForMessage(gBrowser.selectedBrowser.messageManager, + "PopupBlocking:ReplyGetBlockedPopupList"); + notification.querySelector("button").doCommand(); + let popup_event = yield popupShown; + let menu = popup_event.target; + is(menu.id, "blockedPopupOptions", "Blocked popup menu shown"); + + yield popupFilled; + // The menu is filled on the same message that we waited for, so let's ensure that it + // had a chance of running before this test code. + yield new Promise(resolve => executeSoon(resolve)); + + // Check the menu contents. + let sep = menu.querySelector("menuseparator"); + let popupCount = 0; + for (let i = sep.nextElementSibling; i; i = i.nextElementSibling) { + popupCount++; + } + is(popupCount, 2, "Two popups were blocked"); + + // Pressing "allow" should open all blocked popups. + let popupTabs = []; + function onTabOpen(event) { + popupTabs.push(event.target); + } + gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen); + + // Press the button. + let allow = menu.querySelector("[observes='blockedPopupAllowSite']"); + allow.doCommand(); + yield BrowserTestUtils.waitForCondition(() => + popupTabs.length == 2 && + popupTabs.every(aTab => aTab.linkedBrowser.currentURI.spec != "about:blank")); + + gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen); + + is(popupTabs[0].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,a", "Popup a"); + is(popupTabs[1].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,b", "Popup b"); + + // Clean up. + gBrowser.removeTab(tab); + for (let popup of popupTabs) { + gBrowser.removeTab(popup); + } + clearAllPermissionsByPrefix("popup"); + // Ensure the menu closes. + menu.hidePopup(); +}); + +add_task(function* check_icon_hides() { + // Enable the popup blocker. + yield SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]}); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, baseURL + "popup_blocker.html"); + + let button = document.getElementById("page-report-button"); + yield BrowserTestUtils.waitForCondition(() => + gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked")); + ok(!button.hidden, "Button should be visible"); + + let otherPageLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + openLinkIn(baseURL, "current", {}); + yield otherPageLoaded; + ok(button.hidden, "Button should have hidden again after another page loaded."); + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/popups/popup_blocker.html b/browser/base/content/test/popups/popup_blocker.html new file mode 100644 index 000000000..6e2b7db15 --- /dev/null +++ b/browser/base/content/test/popups/popup_blocker.html @@ -0,0 +1,13 @@ +<!doctype html> +<html> + <head> + <meta charset="UTF-8"> + <title>Page creating two popups</title> + </head> + <body> + <script type="text/javascript"> + window.open("data:text/plain;charset=utf-8,a", "a"); + window.open("data:text/plain;charset=utf-8,b", "b"); + </script> + </body> +</html> diff --git a/browser/base/content/test/referrer/.eslintrc.js b/browser/base/content/test/referrer/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/referrer/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/referrer/browser.ini b/browser/base/content/test/referrer/browser.ini new file mode 100644 index 000000000..13b712850 --- /dev/null +++ b/browser/base/content/test/referrer/browser.ini @@ -0,0 +1,24 @@ +[DEFAULT] +support-files = + file_referrer_policyserver.sjs + file_referrer_policyserver_attr.sjs + file_referrer_testserver.sjs + head.js + +[browser_referrer_middle_click.js] +[browser_referrer_middle_click_in_container.js] +[browser_referrer_open_link_in_private.js] +skip-if = os == 'linux' # Bug 1145199 +[browser_referrer_open_link_in_tab.js] +skip-if = os == 'linux' # Bug 1144816 +[browser_referrer_open_link_in_window.js] +skip-if = os == 'linux' # Bug 1145199 +[browser_referrer_open_link_in_window_in_container.js] +skip-if = os == 'linux' # Bug 1145199 +[browser_referrer_simple_click.js] +[browser_referrer_open_link_in_container_tab.js] +skip-if = os == 'linux' # Bug 1144816 +[browser_referrer_open_link_in_container_tab2.js] +skip-if = os == 'linux' # Bug 1144816 +[browser_referrer_open_link_in_container_tab3.js] +skip-if = os == 'linux' # Bug 1144816 diff --git a/browser/base/content/test/referrer/browser_referrer_middle_click.js b/browser/base/content/test/referrer/browser_referrer_middle_click.js new file mode 100644 index 000000000..e6e01c6a3 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_middle_click.js @@ -0,0 +1,20 @@ +// Tests referrer on middle-click navigation. +// Middle-clicks on the link, which opens it in a new tab. + +function startMiddleClickTestCase(aTestNumber) { + info("browser_referrer_middle_click: " + + getReferrerTestDescription(aTestNumber)); + someTabLoaded(gTestWindow).then(function(aNewTab) { + BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => { + checkReferrerAndStartNextTest(aTestNumber, null, aNewTab, + startMiddleClickTestCase); + }); + }); + + clickTheLink(gTestWindow, "testlink", {button: 1}); +} + +function test() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startMiddleClickTestCase); +} diff --git a/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js b/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js new file mode 100644 index 000000000..e89b891f3 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js @@ -0,0 +1,27 @@ +// Tests referrer on middle-click navigation. +// Middle-clicks on the link, which opens it in a new tab, same container. + +function startMiddleClickTestCase(aTestNumber) { + info("browser_referrer_middle_click: " + + getReferrerTestDescription(aTestNumber)); + someTabLoaded(gTestWindow).then(function(aNewTab) { + BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => { + checkReferrerAndStartNextTest(aTestNumber, null, aNewTab, + startMiddleClickTestCase, + { userContextId: 3 }); + }); + }); + + clickTheLink(gTestWindow, "testlink", {button: 1}); +} + +function test() { + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + {set: [["privacy.userContext.enabled", true]]}, + function() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startMiddleClickTestCase, { userContextId: 3 }); + }); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js new file mode 100644 index 000000000..deaf90fb9 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js @@ -0,0 +1,59 @@ +// Tests referrer on context menu navigation - open link in new container tab. +// Selects "open link in new container tab" from the context menu. + +function getReferrerTest(aTestNumber) { + let test = _referrerTests[aTestNumber]; + if (test) { + // We want all the referrer tests to fail! + test.result = ""; + } + + return test; +} + +function startNewTabTestCase(aTestNumber) { + info("browser_referrer_open_link_in_container_tab: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + someTabLoaded(gTestWindow).then(function(aNewTab) { + gTestWindow.gBrowser.selectedTab = aNewTab; + + checkReferrerAndStartNextTest(aTestNumber, null, aNewTab, + startNewTabTestCase); + }); + + let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu"); + + let menupopup = menu.menupopup; + menu.addEventListener("popupshown", function onPopupShown() { + menu.removeEventListener("popupshown", onPopupShown); + + is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup."); + ok(menupopup.firstChild, "We have a first container entry."); + + let firstContext = menupopup.firstChild; + is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry."); + ok(firstContext.hasAttribute("data-usercontextid"), "We have a usercontextid value."); + + aContextMenu.addEventListener("popuphidden", function onPopupHidden() { + aContextMenu.removeEventListener("popuphidden", onPopupHidden); + firstContext.doCommand(); + }); + + aContextMenu.hidePopup(); + }); + + menupopup.showPopup(); + }); +} + +function test() { + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + {set: [["privacy.userContext.enabled", true]]}, + function() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewTabTestCase); + }); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js new file mode 100644 index 000000000..77a5645c6 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js @@ -0,0 +1,31 @@ +// Tests referrer on context menu navigation - open link in new container tab. +// Selects "open link in new container tab" from the context menu. + +// The test runs from a container ID 1. +// Output: we have the correct referrer policy applied. + +function startNewTabTestCase(aTestNumber) { + info("browser_referrer_open_link_in_container_tab: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + someTabLoaded(gTestWindow).then(function(aNewTab) { + gTestWindow.gBrowser.selectedTab = aNewTab; + + checkReferrerAndStartNextTest(aTestNumber, null, aNewTab, + startNewTabTestCase, { userContextId: 1 }); + }); + + doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkincontainertab"); + }); +} + +function test() { + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + {set: [["privacy.userContext.enabled", true]]}, + function() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewTabTestCase, { userContextId: 1 }); + }); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js new file mode 100644 index 000000000..c0a73d828 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js @@ -0,0 +1,63 @@ +// Tests referrer on context menu navigation - open link in new container tab. +// Selects "open link in new container tab" from the context menu. + +// The test runs from a container ID 2. +// Output: we have no referrer. + +function getReferrerTest(aTestNumber) { + let test = _referrerTests[aTestNumber]; + if (test) { + // We want all the referrer tests to fail! + test.result = ""; + } + + return test; +} + +function startNewTabTestCase(aTestNumber) { + info("browser_referrer_open_link_in_container_tab: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + someTabLoaded(gTestWindow).then(function(aNewTab) { + gTestWindow.gBrowser.selectedTab = aNewTab; + + checkReferrerAndStartNextTest(aTestNumber, null, aNewTab, + startNewTabTestCase, { userContextId: 2 }); + }); + + let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu"); + + let menupopup = menu.menupopup; + menu.addEventListener("popupshown", function onPopupShown() { + menu.removeEventListener("popupshown", onPopupShown); + + is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup."); + ok(menupopup.firstChild, "We have a first container entry."); + + let firstContext = menupopup.firstChild; + is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry."); + ok(firstContext.hasAttribute("data-usercontextid"), "We have a usercontextid value."); + is("0", firstContext.getAttribute("data-usercontextid"), "We have the right usercontextid value."); + + aContextMenu.addEventListener("popuphidden", function onPopupHidden() { + aContextMenu.removeEventListener("popuphidden", onPopupHidden); + firstContext.doCommand(); + }); + + aContextMenu.hidePopup(); + }); + + menupopup.showPopup(); + }); +} + +function test() { + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + {set: [["privacy.userContext.enabled", true]]}, + function() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewTabTestCase, { userContextId: 2 }); + }); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js new file mode 100644 index 000000000..8f12e3824 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js @@ -0,0 +1,22 @@ +// Tests referrer on context menu navigation - open link in new private window. +// Selects "open link in new private window" from the context menu. + +function startNewPrivateWindowTestCase(aTestNumber) { + info("browser_referrer_open_link_in_private: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + newWindowOpened().then(function(aNewWindow) { + BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() { + checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null, + startNewPrivateWindowTestCase); + }); + }); + + doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkprivate"); + }); +} + +function test() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewPrivateWindowTestCase); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js new file mode 100644 index 000000000..03119cb57 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js @@ -0,0 +1,21 @@ +// Tests referrer on context menu navigation - open link in new tab. +// Selects "open link in new tab" from the context menu. + +function startNewTabTestCase(aTestNumber) { + info("browser_referrer_open_link_in_tab: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + someTabLoaded(gTestWindow).then(function(aNewTab) { + gTestWindow.gBrowser.selectedTab = aNewTab; + checkReferrerAndStartNextTest(aTestNumber, null, aNewTab, + startNewTabTestCase); + }); + + doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkintab"); + }); +} + +function test() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewTabTestCase); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js new file mode 100644 index 000000000..81e7b2648 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js @@ -0,0 +1,22 @@ +// Tests referrer on context menu navigation - open link in new window. +// Selects "open link in new window" from the context menu. + +function startNewWindowTestCase(aTestNumber) { + info("browser_referrer_open_link_in_window: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + newWindowOpened().then(function(aNewWindow) { + BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() { + checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null, + startNewWindowTestCase); + }); + }); + + doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink"); + }); +} + +function test() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewWindowTestCase); +} diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js new file mode 100644 index 000000000..d5ce87952 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js @@ -0,0 +1,32 @@ +// Tests referrer on context menu navigation - open link in new window. +// Selects "open link in new window" from the context menu. + +// This test runs from a container tab. The new tab/window will be loaded in +// the same container. + +function startNewWindowTestCase(aTestNumber) { + info("browser_referrer_open_link_in_window: " + + getReferrerTestDescription(aTestNumber)); + contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) { + newWindowOpened().then(function(aNewWindow) { + BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() { + checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null, + startNewWindowTestCase, + { userContextId: 1 }); + }); + }); + + doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink"); + }); +} + +function test() { + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + {set: [["privacy.userContext.enabled", true]]}, + function() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startNewWindowTestCase, { userContextId: 1 }); + }); +} diff --git a/browser/base/content/test/referrer/browser_referrer_simple_click.js b/browser/base/content/test/referrer/browser_referrer_simple_click.js new file mode 100644 index 000000000..7f3784e64 --- /dev/null +++ b/browser/base/content/test/referrer/browser_referrer_simple_click.js @@ -0,0 +1,20 @@ +// Tests referrer on simple click navigation. +// Clicks on the link, which opens it in the same tab. + +function startSimpleClickTestCase(aTestNumber) { + info("browser_referrer_simple_click: " + + getReferrerTestDescription(aTestNumber)); + BrowserTestUtils.browserLoaded(gTestWindow.gBrowser.selectedBrowser, false, + (url) => url.endsWith("file_referrer_testserver.sjs")) + .then(function() { + checkReferrerAndStartNextTest(aTestNumber, null, null, + startSimpleClickTestCase); + }); + + clickTheLink(gTestWindow, "testlink", {}); +} + +function test() { + requestLongerTimeout(10); // slowwww shutdown on e10s + startReferrerTest(startSimpleClickTestCase); +} diff --git a/browser/base/content/test/referrer/file_referrer_policyserver.sjs b/browser/base/content/test/referrer/file_referrer_policyserver.sjs new file mode 100644 index 000000000..e07965675 --- /dev/null +++ b/browser/base/content/test/referrer/file_referrer_policyserver.sjs @@ -0,0 +1,37 @@ +/** + * Renders a link with the provided referrer policy. + * Used in browser_referrer_*.js, bug 1113431. + * Arguments: ?scheme=http://&policy=origin&rel=noreferrer + */ +function handleRequest(request, response) +{ + Components.utils.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + + let scheme = query.get("scheme"); + let policy = query.get("policy"); + let rel = query.get("rel"); + + let linkUrl = scheme + + "test1.example.com/browser/browser/base/content/test/referrer/" + + "file_referrer_testserver.sjs"; + let metaReferrerTag = + policy ? `<meta name='referrer' content='${policy}'>` : ""; + + let html = `<!DOCTYPE HTML> + <html> + <head> + <meta charset='utf-8'> + ${metaReferrerTag} + <title>Test referrer</title> + </head> + <body> + <a id='testlink' href='${linkUrl}' ${rel ? ` rel='${rel}'` : ""}> + referrer test link</a> + </body> + </html>`; + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + response.write(html); +} diff --git a/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs b/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs new file mode 100644 index 000000000..25a58188a --- /dev/null +++ b/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs @@ -0,0 +1,36 @@ +/** + * Renders a link with the provided referrer policy. + * Used in browser_referrer_*.js, bug 1113431. + * Arguments: ?scheme=http://&policy=origin&rel=noreferrer + */ +function handleRequest(request, response) +{ + Components.utils.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + + let scheme = query.get("scheme"); + let policy = query.get("policy"); + let rel = query.get("rel"); + + let linkUrl = scheme + + "test1.example.com/browser/browser/base/content/test/referrer/" + + "file_referrer_testserver.sjs"; + let referrerPolicy = + policy ? `referrerpolicy="${policy}"` : ""; + + let html = `<!DOCTYPE HTML> + <html> + <head> + <meta charset='utf-8'> + <title>Test referrer</title> + </head> + <body> + <a id='testlink' href='${linkUrl}' ${referrerPolicy} ${rel ? ` rel='${rel}'` : ""}> + referrer test link</a> + </body> + </html>`; + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + response.write(html); +} diff --git a/browser/base/content/test/referrer/file_referrer_testserver.sjs b/browser/base/content/test/referrer/file_referrer_testserver.sjs new file mode 100644 index 000000000..0cfc53b2c --- /dev/null +++ b/browser/base/content/test/referrer/file_referrer_testserver.sjs @@ -0,0 +1,31 @@ +/** + * Renders the HTTP Referer header up to the second path slash. + * Used in browser_referrer_*.js, bug 1113431. + */ +function handleRequest(request, response) +{ + let referrer = ""; + try { + referrer = request.getHeader("referer"); + } catch (e) { + referrer = ""; + } + + // Strip it past the first path slash. Makes tests easier to read. + referrer = referrer.split("/").slice(0, 4).join("/"); + + let html = `<!DOCTYPE HTML> + <html> + <head> + <meta charset='utf-8'> + <title>Test referrer</title> + </head> + <body> + <div id='testdiv'>${referrer}</div> + </body> + </html>`; + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + response.write(html); +} diff --git a/browser/base/content/test/referrer/head.js b/browser/base/content/test/referrer/head.js new file mode 100644 index 000000000..1a5d5b051 --- /dev/null +++ b/browser/base/content/test/referrer/head.js @@ -0,0 +1,265 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils", + "resource://testing-common/BrowserTestUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ContentTask", + "resource://testing-common/ContentTask.jsm"); + +const REFERRER_URL_BASE = "/browser/browser/base/content/test/referrer/"; +const REFERRER_POLICYSERVER_URL = + "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver.sjs"; +const REFERRER_POLICYSERVER_URL_ATTRIBUTE = + "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver_attr.sjs"; + +SpecialPowers.pushPrefEnv({"set": [['network.http.enablePerElementReferrer', true]]}); + +var gTestWindow = null; +var rounds = 0; + +// We test that the UI code propagates three pieces of state - the referrer URI +// itself, the referrer policy, and the triggering principal. After that, we +// trust nsIWebNavigation to do the right thing with the info it's given, which +// is covered more exhaustively by dom/base/test/test_bug704320.html (which is +// a faster content-only test). So, here, we limit ourselves to cases that +// would break when the UI code drops either of these pieces; we don't try to +// duplicate the entire cross-product test in bug 704320 - that would be slow, +// especially when we're opening a new window for each case. +var _referrerTests = [ + // 1. Normal cases - no referrer policy, no special attributes. + // We expect a full referrer normally, and no referrer on downgrade. + { + fromScheme: "http://", + toScheme: "http://", + result: "http://test1.example.com/browser" // full referrer + }, + { + fromScheme: "https://", + toScheme: "http://", + result: "" // no referrer when downgrade + }, + // 2. Origin referrer policy - we expect an origin referrer, + // even on downgrade. But rel=noreferrer trumps this. + { + fromScheme: "https://", + toScheme: "http://", + policy: "origin", + result: "https://test1.example.com/" // origin, even on downgrade + }, + { + fromScheme: "https://", + toScheme: "http://", + policy: "origin", + rel: "noreferrer", + result: "" // rel=noreferrer trumps meta-referrer + }, + // 3. XXX: using no-referrer here until we support all attribute values (bug 1178337) + // Origin-when-cross-origin policy - this depends on the triggering + // principal. We expect full referrer for same-origin requests, + // and origin referrer for cross-origin requests. + { + fromScheme: "https://", + toScheme: "https://", + policy: "no-referrer", + result: "" // same origin https://test1.example.com/browser + }, + { + fromScheme: "http://", + toScheme: "https://", + policy: "no-referrer", + result: "" // cross origin http://test1.example.com + }, +]; + +/** + * Returns the test object for a given test number. + * @param aTestNumber The test number - 0, 1, 2, ... + * @return The test object, or undefined if the number is out of range. + */ +function getReferrerTest(aTestNumber) { + return _referrerTests[aTestNumber]; +} + +/** + * Returns a brief summary of the test, for logging. + * @param aTestNumber The test number - 0, 1, 2... + * @return The test description. + */ +function getReferrerTestDescription(aTestNumber) { + let test = getReferrerTest(aTestNumber); + return "policy=[" + test.policy + "] " + + "rel=[" + test.rel + "] " + + test.fromScheme + " -> " + test.toScheme; +} + +/** + * Clicks the link. + * @param aWindow The window to click the link in. + * @param aLinkId The id of the link element. + * @param aOptions The options for synthesizeMouseAtCenter. + */ +function clickTheLink(aWindow, aLinkId, aOptions) { + return BrowserTestUtils.synthesizeMouseAtCenter( + "#" + aLinkId, aOptions, aWindow.gBrowser.selectedBrowser); +} + +/** + * Extracts the referrer result from the target window. + * @param aWindow The window where the referrer target has loaded. + * @return {Promise} + * @resolves When extacted, with the text of the (trimmed) referrer. + */ +function referrerResultExtracted(aWindow) { + return ContentTask.spawn(aWindow.gBrowser.selectedBrowser, {}, function() { + return content.document.getElementById("testdiv").textContent; + }); +} + +/** + * Waits for browser delayed startup to finish. + * @param aWindow The window to wait for. + * @return {Promise} + * @resolves When the window is loaded. + */ +function delayedStartupFinished(aWindow) { + return new Promise(function(resolve) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + resolve(); + } + }, "browser-delayed-startup-finished", false); + }); +} + +/** + * Waits for some (any) tab to load. The caller triggers the load. + * @param aWindow The window where to wait for a tab to load. + * @return {Promise} + * @resolves With the tab once it's loaded. + */ +function someTabLoaded(aWindow) { + return BrowserTestUtils.waitForNewTab(gTestWindow.gBrowser).then((tab) => { + return BrowserTestUtils.browserStopped(tab.linkedBrowser).then(() => tab); + }); +} + +/** + * Waits for a new window to open and load. The caller triggers the open. + * @return {Promise} + * @resolves With the new window once it's open and loaded. + */ +function newWindowOpened() { + return TestUtils.topicObserved("browser-delayed-startup-finished") + .then(([win]) => win); +} + +/** + * Opens the context menu. + * @param aWindow The window to open the context menu in. + * @param aLinkId The id of the link to open the context menu on. + * @return {Promise} + * @resolves With the menu popup when the context menu is open. + */ +function contextMenuOpened(aWindow, aLinkId) { + let popupShownPromise = BrowserTestUtils.waitForEvent(aWindow.document, + "popupshown"); + // Simulate right-click. + clickTheLink(aWindow, aLinkId, { type: "contextmenu", button: 2 }); + return popupShownPromise.then(e => e.target); +} + +/** + * Performs a context menu command. + * @param aWindow The window with the already open context menu. + * @param aMenu The menu popup to hide. + * @param aItemId The id of the menu item to activate. + */ +function doContextMenuCommand(aWindow, aMenu, aItemId) { + let command = aWindow.document.getElementById(aItemId); + command.doCommand(); + aMenu.hidePopup(); +} + +/** + * Loads a single test case, i.e., a source url into gTestWindow. + * @param aTestNumber The test case number - 0, 1, 2... + * @return {Promise} + * @resolves When the source url for this test case is loaded. + */ +function referrerTestCaseLoaded(aTestNumber, aParams) { + let test = getReferrerTest(aTestNumber); + let server = rounds == 0 ? REFERRER_POLICYSERVER_URL : + REFERRER_POLICYSERVER_URL_ATTRIBUTE; + let url = test.fromScheme + server + + "?scheme=" + escape(test.toScheme) + + "&policy=" + escape(test.policy || "") + + "&rel=" + escape(test.rel || ""); + let browser = gTestWindow.gBrowser; + return BrowserTestUtils.openNewForegroundTab(browser, () => { + browser.selectedTab = browser.addTab(url, aParams); + }, false, true); +} + +/** + * Checks the result of the referrer test, and moves on to the next test. + * @param aTestNumber The test number - 0, 1, 2, ... + * @param aNewWindow The new window where the referrer target opened, or null. + * @param aNewTab The new tab where the referrer target opened, or null. + * @param aStartTestCase The callback to start the next test, aTestNumber + 1. + */ +function checkReferrerAndStartNextTest(aTestNumber, aNewWindow, aNewTab, + aStartTestCase, aParams = {}) { + referrerResultExtracted(aNewWindow || gTestWindow).then(function(result) { + // Compare the actual result against the expected one. + let test = getReferrerTest(aTestNumber); + let desc = getReferrerTestDescription(aTestNumber); + is(result, test.result, desc); + + // Clean up - close new tab / window, and then the source tab. + aNewTab && (aNewWindow || gTestWindow).gBrowser.removeTab(aNewTab); + aNewWindow && aNewWindow.close(); + is(gTestWindow.gBrowser.tabs.length, 2, "two tabs open"); + gTestWindow.gBrowser.removeTab(gTestWindow.gBrowser.tabs[1]); + + // Move on to the next test. Or finish if we're done. + var nextTestNumber = aTestNumber + 1; + if (getReferrerTest(nextTestNumber)) { + referrerTestCaseLoaded(nextTestNumber, aParams).then(function() { + aStartTestCase(nextTestNumber); + }); + } else if (rounds == 0) { + nextTestNumber = 0; + rounds = 1; + referrerTestCaseLoaded(nextTestNumber, aParams).then(function() { + aStartTestCase(nextTestNumber); + }); + } else { + finish(); + } + }); +} + +/** + * Fires up the complete referrer test. + * @param aStartTestCase The callback to start a single test case, called with + * the test number - 0, 1, 2... Needs to trigger the navigation from the source + * page, and call checkReferrerAndStartNextTest() when the target is loaded. + */ +function startReferrerTest(aStartTestCase, params = {}) { + waitForExplicitFinish(); + + // Open the window where we'll load the source URLs. + gTestWindow = openDialog(location, "", "chrome,all,dialog=no", "about:blank"); + registerCleanupFunction(function() { + gTestWindow && gTestWindow.close(); + }); + + // Load and start the first test. + delayedStartupFinished(gTestWindow).then(function() { + referrerTestCaseLoaded(0, params).then(function() { + aStartTestCase(0); + }); + }); +} diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini new file mode 100644 index 000000000..6ad3668fd --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] +support-files = + head.js + +[browser_identityBlock_focus.js] +skip-if = os == 'mac' # Bug 1334418 (try only) +support-files = ../general/permissions.html +[browser_identityPopup_focus.js] diff --git a/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js new file mode 100644 index 000000000..e1e4e537a --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js @@ -0,0 +1,62 @@ +/* Tests that the identity block can be reached via keyboard + * shortcuts and that it has the correct tab order. + */ + +const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com"); +const PERMISSIONS_PAGE = TEST_PATH + "permissions.html"; + +function synthesizeKeyAndWaitForFocus(element, keyCode, options) { + let focused = BrowserTestUtils.waitForEvent(element, "focus"); + EventUtils.synthesizeKey(keyCode, options); + return focused; +} + +// Checks that the identity block is the next element after the urlbar +// to be focused if there are no active notification anchors. +add_task(function* testWithoutNotifications() { + yield BrowserTestUtils.withNewTab("https://example.com", function*() { + yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true}) + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + yield synthesizeKeyAndWaitForFocus(gIdentityHandler._identityBox, "VK_TAB", {shiftKey: true}) + is(document.activeElement, gIdentityHandler._identityBox, + "identity block should be focused"); + }); +}); + +// Checks that when there is a notification anchor, it will receive +// focus before the identity block. +add_task(function* testWithoutNotifications() { + + yield BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, function*(browser) { + let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); + // Request a permission; + BrowserTestUtils.synthesizeMouseAtCenter("#geo", {}, browser); + yield popupshown; + + yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true}) + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + let geoIcon = document.getElementById("geo-notification-icon"); + yield synthesizeKeyAndWaitForFocus(geoIcon, "VK_TAB", {shiftKey: true}) + is(document.activeElement, geoIcon, "notification anchor should be focused"); + yield synthesizeKeyAndWaitForFocus(gIdentityHandler._identityBox, "VK_TAB", {shiftKey: true}) + is(document.activeElement, gIdentityHandler._identityBox, + "identity block should be focused"); + }); +}); + +// Checks that with invalid pageproxystate the identity block is ignored. +add_task(function* testInvalidPageProxyState() { + yield BrowserTestUtils.withNewTab("about:blank", function*(browser) { + // Loading about:blank will automatically focus the urlbar, which, however, can + // race with the test code. So we only send the shortcut if the urlbar isn't focused yet. + if (document.activeElement != gURLBar.inputField) { + yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true}) + } + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + yield synthesizeKeyAndWaitForFocus(gBrowser.getTabForBrowser(browser), "VK_TAB", {shiftKey: true}) + isnot(document.activeElement, gIdentityHandler._identityBox, + "identity block should not be focused"); + // Restore focus to the url bar. + gURLBar.focus(); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js new file mode 100644 index 000000000..eea06f079 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js @@ -0,0 +1,27 @@ +/* Tests the focus behavior of the identity popup. */ + +// Access the identity popup via mouseclick. Focus should not be moved inside. +add_task(function* testIdentityPopupFocusClick() { + yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}); + yield BrowserTestUtils.withNewTab("https://example.com", function*() { + let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown"); + EventUtils.synthesizeMouseAtCenter(gIdentityHandler._identityBox, {}); + yield shown; + isnot(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander")); + }); +}); + +// Access the identity popup via keyboard. Focus should be moved inside. +add_task(function* testIdentityPopupFocusKeyboard() { + yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}); + yield BrowserTestUtils.withNewTab("https://example.com", function*() { + let focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityBox, "focus"); + gIdentityHandler._identityBox.focus(); + yield focused; + let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown"); + EventUtils.synthesizeKey(" ", {}); + yield shown; + is(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander")); + }); +}); + diff --git a/browser/base/content/test/siteIdentity/head.js b/browser/base/content/test/siteIdentity/head.js new file mode 100644 index 000000000..12a0547ee --- /dev/null +++ b/browser/base/content/test/siteIdentity/head.js @@ -0,0 +1,6 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); diff --git a/browser/base/content/test/social/.eslintrc.js b/browser/base/content/test/social/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/social/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/social/blocklist.xml b/browser/base/content/test/social/blocklist.xml new file mode 100644 index 000000000..2e3665c36 --- /dev/null +++ b/browser/base/content/test/social/blocklist.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist"> + <emItems> + <emItem blockID="s1" id="test1.example.com@services.mozilla.org"></emItem> + </emItems> +</blocklist> diff --git a/browser/base/content/test/social/browser.ini b/browser/base/content/test/social/browser.ini new file mode 100644 index 000000000..91f931602 --- /dev/null +++ b/browser/base/content/test/social/browser.ini @@ -0,0 +1,23 @@ +[DEFAULT] +support-files = + blocklist.xml + head.js + opengraph/og_invalid_url.html + opengraph/opengraph.html + opengraph/shortlink_linkrel.html + opengraph/shorturl_link.html + opengraph/shorturl_linkrel.html + microformats.html + share.html + share_activate.html + social_activate.html + social_activate_basic.html + social_activate_iframe.html + social_postActivation.html + !/browser/base/content/test/plugins/blockNoPlugins.xml + +[browser_aboutHome_activation.js] +[browser_addons.js] +[browser_blocklist.js] +[browser_share.js] +[browser_social_activation.js] diff --git a/browser/base/content/test/social/browser_aboutHome_activation.js b/browser/base/content/test/social/browser_aboutHome_activation.js new file mode 100644 index 000000000..37cca53d2 --- /dev/null +++ b/browser/base/content/test/social/browser_aboutHome_activation.js @@ -0,0 +1,229 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", + "resource:///modules/AboutHome.jsm"); + +var snippet = +' <script>' + +' var manifest = {' + +' "name": "Demo Social Service",' + +' "origin": "https://example.com",' + +' "iconURL": "chrome://branding/content/icon16.png",' + +' "icon32URL": "chrome://branding/content/icon32.png",' + +' "icon64URL": "chrome://branding/content/icon64.png",' + +' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' + +' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' + +' };' + +' function activateProvider(node) {' + +' node.setAttribute("data-service", JSON.stringify(manifest));' + +' var event = new CustomEvent("ActivateSocialFeature");' + +' node.dispatchEvent(event);' + +' }' + +' </script>' + +' <div id="activationSnippet" onclick="activateProvider(this)">' + +' <img src="chrome://branding/content/icon32.png"></img>' + +' </div>'; + +// enable one-click activation +var snippet2 = +' <script>' + +' var manifest = {' + +' "name": "Demo Social Service",' + +' "origin": "https://example.com",' + +' "iconURL": "chrome://branding/content/icon16.png",' + +' "icon32URL": "chrome://branding/content/icon32.png",' + +' "icon64URL": "chrome://branding/content/icon64.png",' + +' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' + +' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' + +' "oneclick": true' + +' };' + +' function activateProvider(node) {' + +' node.setAttribute("data-service", JSON.stringify(manifest));' + +' var event = new CustomEvent("ActivateSocialFeature");' + +' node.dispatchEvent(event);' + +' }' + +' </script>' + +' <div id="activationSnippet" onclick="activateProvider(this)">' + +' <img src="chrome://branding/content/icon32.png"></img>' + +' </div>'; + +var gTests = [ + +{ + desc: "Test activation with enable panel", + snippet: snippet, + panel: true +}, + +{ + desc: "Test activation bypassing enable panel", + snippet: snippet2, + panel: false +} +]; + +function test() +{ + waitForExplicitFinish(); + requestLongerTimeout(2); + ignoreAllUncaughtExceptions(); + PopupNotifications.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + PopupNotifications.panel.removeAttribute("animate"); + }); + + Task.spawn(function* () { + for (let test of gTests) { + info(test.desc); + + // Create a tab to run the test. + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + // Add an event handler to modify the snippets map once it's ready. + let snippetsPromise = promiseSetupSnippetsMap(tab, test.snippet); + + // Start loading about:home and wait for it to complete, snippets should be loaded + yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsCompleted"); + + yield snippetsPromise; + + // ensure our activation snippet is indeed available + yield ContentTask.spawn(tab.linkedBrowser, {}, function*(arg) { + ok(!!content.document.getElementById("snippets"), "Found snippets element"); + ok(!!content.document.getElementById("activationSnippet"), "The snippet is present."); + }); + + yield new Promise(resolve => { + activateProvider(tab, test.panel).then(() => { + checkSocialUI(); + SocialService.uninstallProvider("https://example.com", function () { + info("provider uninstalled"); + resolve(); + }); + }); + }); + + // activation opened a post-activation info tab, close it. + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + yield BrowserTestUtils.removeTab(tab); + } + }).then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} + +/** + * Starts a load in an existing tab and waits for it to finish (via some event). + * + * @param aTab + * The tab to load into. + * @param aUrl + * The url to load. + * @param aEvent + * The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. + */ +function promiseTabLoadEvent(aTab, aURL, aEventType="load") +{ + return new Promise(resolve => { + info("Wait tab event: " + aEventType); + aTab.linkedBrowser.addEventListener(aEventType, function load(event) { + if (event.originalTarget != aTab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank") { + info("skipping spurious load event"); + return; + } + aTab.linkedBrowser.removeEventListener(aEventType, load, true); + info("Tab event received: " + aEventType); + resolve(); + }, true, true); + aTab.linkedBrowser.loadURI(aURL); + }); +} + +/** + * Cleans up snippets and ensures that by default we don't try to check for + * remote snippets since that may cause network bustage or slowness. + * + * @param aTab + * The tab containing about:home. + * @param aSetupFn + * The setup function to be run. + * @return {Promise} resolved when the snippets are ready. Gets the snippets map. + */ +function promiseSetupSnippetsMap(aTab, aSnippet) +{ + info("Waiting for snippets map"); + + return ContentTask.spawn(aTab.linkedBrowser, + {snippetsVersion: AboutHomeUtils.snippetsVersion, + snippet: aSnippet}, + function*(arg) { + return new Promise(resolve => { + addEventListener("AboutHomeLoadSnippets", function load(event) { + removeEventListener("AboutHomeLoadSnippets", load, true); + + let cw = content.window.wrappedJSObject; + + // The snippets should already be ready by this point. Here we're + // just obtaining a reference to the snippets map. + cw.ensureSnippetsMapThen(function (aSnippetsMap) { + aSnippetsMap = Cu.waiveXrays(aSnippetsMap); + console.log("Got snippets map: " + + "{ last-update: " + aSnippetsMap.get("snippets-last-update") + + ", cached-version: " + aSnippetsMap.get("snippets-cached-version") + + " }"); + // Don't try to update. + aSnippetsMap.set("snippets-last-update", Date.now()); + aSnippetsMap.set("snippets-cached-version", arg.snippetsVersion); + // Clear snippets. + aSnippetsMap.delete("snippets"); + aSnippetsMap.set("snippets", arg.snippet); + resolve(); + }); + }, true, true); + }); + }); +} + + +function sendActivationEvent(tab) { + // hack Social.lastEventReceived so we don't hit the "too many events" check. + Social.lastEventReceived = 0; + let doc = tab.linkedBrowser.contentDocument; + // if our test has a frame, use it + if (doc.defaultView.frames[0]) + doc = doc.defaultView.frames[0].document; + let button = doc.getElementById("activationSnippet"); + BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser); +} + +function activateProvider(tab, expectPanel, aCallback) { + return new Promise(resolve => { + if (expectPanel) { + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => { + let panel = document.getElementById("servicesInstall-notification"); + panel.button.click(); + }); + } + waitForProviderLoad().then(() => { + checkSocialUI(); + resolve(); + }); + sendActivationEvent(tab); + }); +} + +function waitForProviderLoad(cb) { + return Promise.all([ + promiseObserverNotified("social:provider-enabled"), + ensureFrameLoaded(gBrowser, "https://example.com/browser/browser/base/content/test/social/social_postActivation.html"), + ]); +} diff --git a/browser/base/content/test/social/browser_addons.js b/browser/base/content/test/social/browser_addons.js new file mode 100644 index 000000000..5a75d1d67 --- /dev/null +++ b/browser/base/content/test/social/browser_addons.js @@ -0,0 +1,217 @@ +var AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager; +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +var manifest = { + name: "provider 1", + origin: "https://example.com", + shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html", + iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png" +}; +var manifest2 = { // used for testing install + name: "provider 2", + origin: "https://test1.example.com", + shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html", + iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png", + version: "1.0" +}; +var manifestUpgrade = { // used for testing install + name: "provider 3", + origin: "https://test2.example.com", + shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html", + iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png", + version: "1.0" +}; + +function test() { + waitForExplicitFinish(); + PopupNotifications.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + PopupNotifications.panel.removeAttribute("animate"); + }); + + let prefname = getManifestPrefname(manifest); + // ensure that manifest2 is NOT showing as builtin + is(SocialService.getOriginActivationType(manifest.origin), "foreign", "manifest is foreign"); + is(SocialService.getOriginActivationType(manifest2.origin), "foreign", "manifest2 is foreign"); + + Services.prefs.setBoolPref("social.remote-install.enabled", true); + runSocialTests(tests, undefined, undefined, function () { + Services.prefs.clearUserPref("social.remote-install.enabled"); + ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs"); + // just in case the tests failed, clear these here as well + Services.prefs.clearUserPref("social.directories"); + finish(); + }); +} + +function installListener(next, aManifest) { + let expectEvent = "onInstalling"; + let prefname = getManifestPrefname(aManifest); + // wait for the actual removal to call next + SocialService.registerProviderListener(function providerListener(topic, origin, providers) { + if (topic == "provider-disabled") { + SocialService.unregisterProviderListener(providerListener); + is(origin, aManifest.origin, "provider disabled"); + executeSoon(next); + } + }); + + return { + onInstalling: function(addon) { + is(expectEvent, "onInstalling", "install started"); + is(addon.manifest.origin, aManifest.origin, "provider about to be installed"); + ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs"); + expectEvent = "onInstalled"; + }, + onInstalled: function(addon) { + is(addon.manifest.origin, aManifest.origin, "provider installed"); + ok(addon.installDate.getTime() > 0, "addon has installDate"); + ok(addon.updateDate.getTime() > 0, "addon has updateDate"); + ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs"); + expectEvent = "onUninstalling"; + }, + onUninstalling: function(addon) { + is(expectEvent, "onUninstalling", "uninstall started"); + is(addon.manifest.origin, aManifest.origin, "provider about to be uninstalled"); + ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs"); + expectEvent = "onUninstalled"; + }, + onUninstalled: function(addon) { + is(expectEvent, "onUninstalled", "provider has been uninstalled"); + is(addon.manifest.origin, aManifest.origin, "provider uninstalled"); + ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs"); + AddonManager.removeAddonListener(this); + } + }; +} + +var tests = { + testHTTPInstallFailure: function(next) { + let installFrom = "http://example.com"; + is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install"); + let data = { + origin: installFrom, + url: installFrom+"/activate", + manifest: manifest, + window: window + } + Social.installProvider(data, function(addonManifest) { + ok(!addonManifest, "unable to install provider over http"); + next(); + }); + }, + testAddonEnableToggle: function(next) { + let expectEvent; + let prefname = getManifestPrefname(manifest); + let listener = { + onEnabled: function(addon) { + is(expectEvent, "onEnabled", "provider onEnabled"); + ok(!addon.userDisabled, "provider enabled"); + executeSoon(function() { + expectEvent = "onDisabling"; + addon.userDisabled = true; + }); + }, + onEnabling: function(addon) { + is(expectEvent, "onEnabling", "provider onEnabling"); + expectEvent = "onEnabled"; + }, + onDisabled: function(addon) { + is(expectEvent, "onDisabled", "provider onDisabled"); + ok(addon.userDisabled, "provider disabled"); + AddonManager.removeAddonListener(listener); + // clear the provider user-level pref + Services.prefs.clearUserPref(prefname); + executeSoon(next); + }, + onDisabling: function(addon) { + is(expectEvent, "onDisabling", "provider onDisabling"); + expectEvent = "onDisabled"; + } + }; + AddonManager.addAddonListener(listener); + + // we're only testing enable disable, so we quickly set the user-level pref + // for this provider and test enable/disable toggling + setManifestPref(prefname, manifest); + ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs"); + AddonManager.getAddonsByTypes(["service"], function(addons) { + for (let addon of addons) { + if (addon.userDisabled) { + expectEvent = "onEnabling"; + addon.userDisabled = false; + // only test with one addon + return; + } + } + ok(false, "no addons toggled"); + next(); + }); + }, + testProviderEnableToggle: function(next) { + // enable and disabel a provider from the SocialService interface, check + // that the addon manager is updated + + let expectEvent; + let prefname = getManifestPrefname(manifest); + + let listener = { + onEnabled: function(addon) { + is(expectEvent, "onEnabled", "provider onEnabled"); + is(addon.manifest.origin, manifest.origin, "provider enabled"); + ok(!addon.userDisabled, "provider !userDisabled"); + }, + onEnabling: function(addon) { + is(expectEvent, "onEnabling", "provider onEnabling"); + is(addon.manifest.origin, manifest.origin, "provider about to be enabled"); + expectEvent = "onEnabled"; + }, + onDisabled: function(addon) { + is(expectEvent, "onDisabled", "provider onDisabled"); + is(addon.manifest.origin, manifest.origin, "provider disabled"); + ok(addon.userDisabled, "provider userDisabled"); + }, + onDisabling: function(addon) { + is(expectEvent, "onDisabling", "provider onDisabling"); + is(addon.manifest.origin, manifest.origin, "provider about to be disabled"); + expectEvent = "onDisabled"; + } + }; + AddonManager.addAddonListener(listener); + + expectEvent = "onEnabling"; + setManifestPref(prefname, manifest); + SocialService.enableProvider(manifest.origin, function(provider) { + expectEvent = "onDisabling"; + SocialService.disableProvider(provider.origin, function() { + AddonManager.removeAddonListener(listener); + Services.prefs.clearUserPref(prefname); + next(); + }); + }); + }, + testDirectoryInstall: function(next) { + AddonManager.addAddonListener(installListener(next, manifest2)); + + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => { + let panel = document.getElementById("servicesInstall-notification"); + info("servicesInstall-notification panel opened"); + panel.button.click(); + }); + + Services.prefs.setCharPref("social.directories", manifest2.origin); + is(SocialService.getOriginActivationType(manifest2.origin), "directory", "testing directory install"); + let data = { + origin: manifest2.origin, + url: manifest2.origin + "/directory", + manifest: manifest2, + window: window + } + Social.installProvider(data, function(addonManifest) { + Services.prefs.clearUserPref("social.directories"); + SocialService.enableProvider(addonManifest.origin, function(provider) { + Social.uninstallProvider(addonManifest.origin); + }); + }); + } +} diff --git a/browser/base/content/test/social/browser_blocklist.js b/browser/base/content/test/social/browser_blocklist.js new file mode 100644 index 000000000..b67d5efb3 --- /dev/null +++ b/browser/base/content/test/social/browser_blocklist.js @@ -0,0 +1,211 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// a place for miscellaneous social tests + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul"; +var blocklistURL = "http://example.com/browser/browser/base/content/test/social/blocklist.xml"; + +var manifest = { // normal provider + name: "provider ok", + origin: "https://example.com", + shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html", + iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png" +}; +var manifest_bad = { // normal provider + name: "provider blocked", + origin: "https://test1.example.com", + shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html", + iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png" +}; + +// blocklist testing +function updateBlocklist() { + var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] + .getService(Ci.nsITimerCallback); + let promise = promiseObserverNotified("blocklist-updated"); + blocklistNotifier.notify(null); + return promise; +} + +var _originalTestBlocklistURL = null; +function setAndUpdateBlocklist(aURL) { + if (!_originalTestBlocklistURL) + _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); + Services.prefs.setCharPref("extensions.blocklist.url", aURL); + return updateBlocklist(); +} + +function resetBlocklist() { + // XXX - this has "forked" from the head.js helpers in our parent directory :( + // But let's reuse their blockNoPlugins.xml. Later, we should arrange to + // use their head.js helpers directly + let noBlockedURL = "http://example.com/browser/browser/base/content/test/plugins/blockNoPlugins.xml"; + return new Promise(resolve => { + setAndUpdateBlocklist(noBlockedURL).then(() => { + Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); + resolve(); + }); + }); +} + +function test() { + waitForExplicitFinish(); + // turn on logging for nsBlocklistService.js + Services.prefs.setBoolPref("extensions.logging.enabled", true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref("extensions.logging.enabled"); + }); + + runSocialTests(tests, undefined, undefined, function () { + resetBlocklist().then(finish); // restore to original pref + }); +} + +var tests = { + testSimpleBlocklist: function(next) { + // this really just tests adding and clearing our blocklist for later tests + setAndUpdateBlocklist(blocklistURL).then(() => { + ok(Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocking 'blocked'"); + ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest)), "not blocking 'good'"); + resetBlocklist().then(() => { + ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocklist cleared"); + next(); + }); + }); + }, + testAddingNonBlockedProvider: function(next) { + function finishTest(isgood) { + ok(isgood, "adding non-blocked provider ok"); + Services.prefs.clearUserPref("social.manifest.good"); + resetBlocklist().then(next); + } + setManifestPref("social.manifest.good", manifest); + setAndUpdateBlocklist(blocklistURL).then(() => { + try { + SocialService.addProvider(manifest, function(provider) { + try { + SocialService.disableProvider(provider.origin, function() { + ok(true, "added and removed provider"); + finishTest(true); + }); + } catch (e) { + ok(false, "SocialService.disableProvider threw exception: " + e); + finishTest(false); + } + }); + } catch (e) { + ok(false, "SocialService.addProvider threw exception: " + e); + finishTest(false); + } + }); + }, + testAddingBlockedProvider: function(next) { + function finishTest(good) { + ok(good, "Unable to add blocklisted provider"); + Services.prefs.clearUserPref("social.manifest.blocked"); + resetBlocklist().then(next); + } + setManifestPref("social.manifest.blocked", manifest_bad); + setAndUpdateBlocklist(blocklistURL).then(() => { + try { + SocialService.addProvider(manifest_bad, function(provider) { + SocialService.disableProvider(provider.origin, function() { + ok(false, "SocialService.addProvider should throw blocklist exception"); + finishTest(false); + }); + }); + } catch (e) { + ok(true, "SocialService.addProvider should throw blocklist exception: " + e); + finishTest(true); + } + }); + }, + testInstallingBlockedProvider: function(next) { + function finishTest(good) { + ok(good, "Unable to install blocklisted provider"); + resetBlocklist().then(next); + } + let activationURL = manifest_bad.origin + "/browser/browser/base/content/test/social/social_activate.html" + setAndUpdateBlocklist(blocklistURL).then(() => { + try { + // expecting an exception when attempting to install a hard blocked + // provider + let data = { + origin: manifest_bad.origin, + url: activationURL, + manifest: manifest_bad, + window: window + } + Social.installProvider(data, function(addonManifest) { + finishTest(false); + }); + } catch (e) { + finishTest(true); + } + }); + }, + testBlockingExistingProvider: function(next) { + let listener = { + _window: null, + onOpenWindow: function(aXULWindow) { + Services.wm.removeListener(this); + this._window = aXULWindow; + let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + + domwindow.addEventListener("load", function _load() { + domwindow.removeEventListener("load", _load, false); + + domwindow.addEventListener("unload", function _unload() { + domwindow.removeEventListener("unload", _unload, false); + info("blocklist window was closed"); + Services.wm.removeListener(listener); + next(); + }, false); + + is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused"); + // wait until after load to cancel so the dialog has initalized. we + // don't want to accept here since that restarts the browser. + executeSoon(() => { + let cancelButton = domwindow.document.documentElement.getButton("cancel"); + info("***** hit the cancel button\n"); + cancelButton.doCommand(); + }); + }, false); + }, + onCloseWindow: function(aXULWindow) { }, + onWindowTitleChange: function(aXULWindow, aNewTitle) { } + }; + + Services.wm.addListener(listener); + + setManifestPref("social.manifest.blocked", manifest_bad); + try { + SocialService.addProvider(manifest_bad, function(provider) { + // the act of blocking should cause a 'provider-disabled' notification + // from SocialService. + SocialService.registerProviderListener(function providerListener(topic, origin, providers) { + if (topic != "provider-disabled") + return; + SocialService.unregisterProviderListener(providerListener); + is(origin, provider.origin, "provider disabled"); + SocialService.getProvider(provider.origin, function(p) { + ok(p == null, "blocklisted provider disabled"); + Services.prefs.clearUserPref("social.manifest.blocked"); + resetBlocklist(); + }); + }); + // no callback - the act of updating should cause the listener above + // to fire. + setAndUpdateBlocklist(blocklistURL); + }); + } catch (e) { + ok(false, "unable to add provider " + e); + next(); + } + } +} diff --git a/browser/base/content/test/social/browser_share.js b/browser/base/content/test/social/browser_share.js new file mode 100644 index 000000000..19dca519b --- /dev/null +++ b/browser/base/content/test/social/browser_share.js @@ -0,0 +1,396 @@ + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +var baseURL = "https://example.com/browser/browser/base/content/test/social/"; + +var manifest = { // normal provider + name: "provider 1", + origin: "https://example.com", + iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png", + shareURL: "https://example.com/browser/browser/base/content/test/social/share.html" +}; +var activationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html"; + +function sendActivationEvent(subframe) { + // hack Social.lastEventReceived so we don't hit the "too many events" check. + Social.lastEventReceived = 0; + let doc = subframe.contentDocument; + // if our test has a frame, use it + let button = doc.getElementById("activation"); + ok(!!button, "got the activation button"); + EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setCharPref("social.shareDirectory", activationPage); + + let frameScript = "data:,(" + function frame_script() { + addEventListener("OpenGraphData", function (aEvent) { + sendAsyncMessage("sharedata", aEvent.detail); + }, true, true); + /* bug 1042991, ensure history is available by calling history.back on close */ + addMessageListener("closeself", function(e) { + content.history.back(); + content.close(); + }, true); + /* if text is entered into field, onbeforeunload will cause a modal dialog + unless dialogs have been disabled for the iframe. */ + content.onbeforeunload = function(e) { + return 'FAIL.'; + }; + }.toString() + ")();"; + let mm = getGroupMessageManager("social"); + mm.loadFrameScript(frameScript, true); + + // Animation on the panel can cause intermittent failures such as bug 1115131. + SocialShare.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + SocialShare.panel.removeAttribute("animate"); + mm.removeDelayedFrameScript(frameScript); + Services.prefs.clearUserPref("social.directories"); + Services.prefs.clearUserPref("social.shareDirectory"); + Services.prefs.clearUserPref("social.share.activationPanelEnabled"); + }); + runSocialTests(tests, undefined, function(next) { + let shareButton = SocialShare.shareButton; + if (shareButton) { + CustomizableUI.removeWidgetFromArea("social-share-button", CustomizableUI.AREA_NAVBAR) + shareButton.remove(); + } + next(); + }); +} + +var corpus = [ + { + url: baseURL+"opengraph/opengraph.html", + options: { + // og:title + title: ">This is my title<", + // og:description + description: "A test corpus file for open graph tags we care about", + // medium: this.getPageMedium(), + // source: this.getSourceURL(), + // og:url + url: "https://www.mozilla.org/", + // shortUrl: this.getShortURL(), + // og:image + previews:["https://www.mozilla.org/favicon.png"], + // og:site_name + siteName: ">My simple test page<" + } + }, + { + // tests that og:url doesn't override the page url if it is bad + url: baseURL+"opengraph/og_invalid_url.html", + options: { + description: "A test corpus file for open graph tags passing a bad url", + url: baseURL+"opengraph/og_invalid_url.html", + previews: [], + siteName: "Evil chrome delivering website" + } + }, + { + url: baseURL+"opengraph/shorturl_link.html", + options: { + previews: ["http://example.com/1234/56789.jpg"], + url: "http://www.example.com/photos/56789/", + shortUrl: "http://imshort/p/abcde" + } + }, + { + url: baseURL+"opengraph/shorturl_linkrel.html", + options: { + previews: ["http://example.com/1234/56789.jpg"], + url: "http://www.example.com/photos/56789/", + shortUrl: "http://imshort/p/abcde" + } + }, + { + url: baseURL+"opengraph/shortlink_linkrel.html", + options: { + previews: ["http://example.com/1234/56789.jpg"], + url: "http://www.example.com/photos/56789/", + shortUrl: "http://imshort/p/abcde" + } + } +]; + +function hasoptions(testOptions, options) { + for (let option in testOptions) { + let data = testOptions[option]; + info("data: "+JSON.stringify(data)); + let message_data = options[option]; + info("message_data: "+JSON.stringify(message_data)); + if (Array.isArray(data)) { + // the message may have more array elements than we are testing for, this + // is ok since some of those are hard to test. So we just test that + // anything in our test data IS in the message. + ok(Array.every(data, function(item) { return message_data.indexOf(item) >= 0 }), "option "+option); + } else { + is(message_data, data, "option "+option); + } + } +} + +var tests = { + testShareDisabledOnActivation: function(next) { + // starting on about:blank page, share should be visible but disabled when + // adding provider + is(gBrowser.currentURI.spec, "about:blank"); + + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + SocialService.addProvider(manifest, function(provider) { + is(SocialUI.enabled, true, "SocialUI is enabled"); + checkSocialUI(); + // share should not be enabled since we only have about:blank page + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + SocialService.disableProvider(manifest.origin, next); + }); + }, + testShareEnabledOnActivation: function(next) { + // starting from *some* page, share should be visible and enabled when + // activating provider + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + let testData = corpus[0]; + BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => { + SocialService.addProvider(manifest, function(provider) { + is(SocialUI.enabled, true, "SocialUI is enabled"); + checkSocialUI(); + // share should not be enabled since we only have about:blank page + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + BrowserTestUtils.removeTab(tab).then(next); + }); + }); + }, + testSharePage: function(next) { + let testTab; + let testIndex = 0; + let testData = corpus[testIndex++]; + + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + let mm = getGroupMessageManager("social"); + mm.addMessageListener("sharedata", function handler(msg) { + BrowserTestUtils.removeTab(testTab).then(() => { + hasoptions(testData.options, JSON.parse(msg.data)); + testData = corpus[testIndex++]; + BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, "share panel closed").then(() => { + if (testData) { + runOneTest(); + } else { + mm.removeMessageListener("sharedata", handler); + SocialService.disableProvider(manifest.origin, next); + } + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + }); + + function runOneTest() { + BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => { + testTab = tab; + + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + SocialShare.sharePage(manifest.origin); + }); + } + executeSoon(runOneTest); + }, + testShareMicroformats: function(next) { + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + SocialService.addProvider(manifest, function(provider) { + let target, testTab; + + let expecting = JSON.stringify({ + "url": "https://example.com/browser/browser/base/content/test/social/microformats.html", + "title": "Raspberry Pi Page", + "previews": ["https://example.com/someimage.jpg"], + "microformats": { + "items": [{ + "type": ["h-product"], + "properties": { + "name": ["Raspberry Pi"], + "photo": ["https://example.com/someimage.jpg"], + "description": [{ + "value": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.", + "html": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming." + } + ], + "url": ["https://example.com/"], + "price": ["29.95"], + "review": [{ + "value": "4.5 out of 5", + "type": ["h-review"], + "properties": { + "rating": ["4.5"] + } + } + ], + "category": ["Computer", "Education"] + } + } + ], + "rels": { + "tag": ["https://example.com/wiki/computer", "https://example.com/wiki/education"] + }, + "rel-urls": { + "https://example.com/wiki/computer": { + "text": "Computer", + "rels": ["tag"] + }, + "https://example.com/wiki/education": { + "text": "Education", + "rels": ["tag"] + } + } + } + }); + + let mm = getGroupMessageManager("social"); + mm.addMessageListener("sharedata", function handler(msg) { + is(msg.data, expecting, "microformats data ok"); + BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, + "share panel closed").then(() => { + mm.removeMessageListener("sharedata", handler); + BrowserTestUtils.removeTab(testTab).then(() => { + SocialService.disableProvider(manifest.origin, next); + }); + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + + let url = "https://example.com/browser/browser/base/content/test/social/microformats.html" + BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => { + testTab = tab; + + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + let doc = tab.linkedBrowser.contentDocument; + target = doc.getElementById("simple-hcard"); + SocialShare.sharePage(manifest.origin, null, target); + }); + }); + }, + testSharePanelActivation: function(next) { + let testTab; + // cleared in the cleanup function + Services.prefs.setCharPref("social.directories", "https://example.com"); + Services.prefs.setBoolPref("social.share.activationPanelEnabled", true); + // make the iframe so we can wait on the load + SocialShare._createFrame(); + let iframe = SocialShare.iframe; + + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + ensureFrameLoaded(iframe).then(() => { + let subframe = iframe.contentDocument.getElementById("activation-frame"); + ensureFrameLoaded(subframe, activationPage).then(() => { + is(subframe.contentDocument.location.href, activationPage, "activation page loaded"); + promiseObserverNotified("social:provider-enabled").then(() => { + let mm = getGroupMessageManager("social"); + mm.addMessageListener("sharedata", function handler(msg) { + ok(true, "share completed"); + + BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, + "share panel closed").then(() => { + BrowserTestUtils.removeTab(testTab).then(() => { + mm.removeMessageListener("sharedata", handler); + SocialService.uninstallProvider(manifest.origin, next); + }); + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + }); + sendActivationEvent(subframe); + }); + }); + BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => { + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + testTab = tab; + SocialShare.sharePage(); + }); + }, + testSharePanelDialog: function(next) { + let testTab; + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + SocialShare._createFrame(); + + SocialService.addProvider(manifest, () => { + BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => { + ensureFrameLoaded(SocialShare.iframe).then(() => { + // send keys to the input field. An unexpected failure will happen + // if the onbeforeunload handler is fired. + EventUtils.sendKey("f"); + EventUtils.sendKey("a"); + EventUtils.sendKey("i"); + EventUtils.sendKey("l"); + + SocialShare.panel.addEventListener("popuphidden", function hidden(evt) { + SocialShare.panel.removeEventListener("popuphidden", hidden); + let topwin = Services.wm.getMostRecentWindow(null); + is(topwin, window, "no dialog is open"); + + BrowserTestUtils.removeTab(testTab).then(() => { + SocialService.disableProvider(manifest.origin, next); + }); + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + testTab = tab; + SocialShare.sharePage(); + }); + }); + } +} diff --git a/browser/base/content/test/social/browser_social_activation.js b/browser/base/content/test/social/browser_social_activation.js new file mode 100644 index 000000000..2af0d8021 --- /dev/null +++ b/browser/base/content/test/social/browser_social_activation.js @@ -0,0 +1,270 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: Assert is null"); + + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +var tabsToRemove = []; + +function removeProvider(provider) { + return new Promise(resolve => { + // a full install sets the manifest into a pref, addProvider alone doesn't, + // make sure we uninstall if the manifest was added. + if (provider.manifest) { + SocialService.uninstallProvider(provider.origin, resolve); + } else { + SocialService.disableProvider(provider.origin, resolve); + } + }); +} + +function postTestCleanup(callback) { + Task.spawn(function* () { + // any tabs opened by the test. + for (let tab of tabsToRemove) { + yield BrowserTestUtils.removeTab(tab); + } + tabsToRemove = []; + // all the providers may have been added. + while (Social.providers.length > 0) { + yield removeProvider(Social.providers[0]); + } + }).then(callback); +} + +function newTab(url) { + return new Promise(resolve => { + BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => { + tabsToRemove.push(tab); + resolve(tab); + }); + }); +} + +function sendActivationEvent(tab, callback, nullManifest) { + // hack Social.lastEventReceived so we don't hit the "too many events" check. + Social.lastEventReceived = 0; + BrowserTestUtils.synthesizeMouseAtCenter("#activation", {}, tab.linkedBrowser); + executeSoon(callback); +} + +function activateProvider(domain, callback, nullManifest) { + let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_basic.html" + newTab(activationURL).then(tab => { + sendActivationEvent(tab, callback, nullManifest); + }); +} + +function activateIFrameProvider(domain, callback) { + let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html" + newTab(activationURL).then(tab => { + sendActivationEvent(tab, callback, false); + }); +} + +function waitForProviderLoad(origin) { + return Promise.all([ + ensureFrameLoaded(gBrowser, origin + "/browser/browser/base/content/test/social/social_postActivation.html"), + ]); +} + +function getAddonItemInList(aId, aList) { + var item = aList.firstChild; + while (item) { + if ("mAddon" in item && item.mAddon.id == aId) { + aList.ensureElementIsVisible(item); + return item; + } + item = item.nextSibling; + } + return null; +} + +function clickAddonRemoveButton(tab, aCallback) { + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + let addon = aAddons[0]; + + let doc = tab.linkedBrowser.contentDocument; + let list = doc.getElementById("addon-list"); + + let item = getAddonItemInList(addon.id, list); + let button = item._removeBtn; + isnot(button, null, "Should have a remove button"); + ok(!button.disabled, "Button should not be disabled"); + + // uninstall happens after about:addons tab is closed, so we wait on + // disabled + promiseObserverNotified("social:provider-disabled").then(() => { + is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); + executeSoon(function() { aCallback(addon); }); + }); + + BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser); + }); +} + +function activateOneProvider(manifest, finishActivation, aCallback) { + info("activating provider "+manifest.name); + let panel = document.getElementById("servicesInstall-notification"); + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => { + ok(!panel.hidden, "servicesInstall-notification panel opened"); + if (finishActivation) + panel.button.click(); + else + panel.closebutton.click(); + }); + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden").then(() => { + ok(panel.hidden, "servicesInstall-notification panel hidden"); + if (!finishActivation) { + ok(panel.hidden, "activation panel is not showing"); + executeSoon(aCallback); + } else { + waitForProviderLoad(manifest.origin).then(() => { + checkSocialUI(); + executeSoon(aCallback); + }); + } + }); + + // the test will continue as the popup events fire... + activateProvider(manifest.origin, function() { + info("waiting on activation panel to open/close..."); + }); +} + +var gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"]; +var gProviders = [ + { + name: "provider 1", + origin: "https://example.com", + shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html?provider1", + iconURL: "chrome://branding/content/icon48.png" + }, + { + name: "provider 2", + origin: "https://test1.example.com", + shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html?provider2", + iconURL: "chrome://branding/content/icon64.png" + }, + { + name: "provider 3", + origin: "https://test2.example.com", + shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html?provider2", + iconURL: "chrome://branding/content/about-logo.png" + } +]; + + +function test() { + PopupNotifications.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + PopupNotifications.panel.removeAttribute("animate"); + }); + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, () => { + runSocialTests(tests, undefined, postTestCleanup); + }); +} + +var tests = { + testActivationWrongOrigin: function(next) { + // At this stage none of our providers exist, so we expect failure. + Services.prefs.setBoolPref("social.remote-install.enabled", false); + activateProvider(gTestDomains[0], function() { + is(SocialUI.enabled, false, "SocialUI is not enabled"); + let panel = document.getElementById("servicesInstall-notification"); + ok(panel.hidden, "activation panel still hidden"); + checkSocialUI(); + Services.prefs.clearUserPref("social.remote-install.enabled"); + next(); + }); + }, + + testIFrameActivation: function(next) { + activateIFrameProvider(gTestDomains[0], function() { + is(SocialUI.enabled, false, "SocialUI is not enabled"); + let panel = document.getElementById("servicesInstall-notification"); + ok(panel.hidden, "activation panel still hidden"); + checkSocialUI(); + next(); + }); + }, + + testActivationFirstProvider: function(next) { + // first up we add a manifest entry for a single provider. + activateOneProvider(gProviders[0], false, function() { + // we deactivated leaving no providers left, so Social is disabled. + checkSocialUI(); + next(); + }); + }, + + testActivationMultipleProvider: function(next) { + // The trick with this test is to make sure that Social.providers[1] is + // the current provider when doing the undo - this makes sure that the + // Social code doesn't fallback to Social.providers[0], which it will + // do in some cases (but those cases do not include what this test does) + // first enable the 2 providers + SocialService.addProvider(gProviders[0], function() { + SocialService.addProvider(gProviders[1], function() { + checkSocialUI(); + // activate the last provider. + activateOneProvider(gProviders[2], false, function() { + // we deactivated - the first provider should be enabled. + checkSocialUI(); + next(); + }); + }); + }); + }, + + testAddonManagerDoubleInstall: function(next) { + // Create a new tab and load about:addons + let addonsTab = gBrowser.addTab(); + gBrowser.selectedTab = addonsTab; + BrowserOpenAddonsMgr('addons://list/service'); + gBrowser.selectedBrowser.addEventListener("load", function tabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true); + is(addonsTab.linkedBrowser.currentURI.spec, "about:addons", "about:addons should load into blank tab."); + + activateOneProvider(gProviders[0], true, function() { + info("first activation completed"); + is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded"); + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => { + is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_activate_basic.html", "activation page selected"); + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => { + tabsToRemove.pop(); + // uninstall the provider + clickAddonRemoveButton(addonsTab, function(addon) { + checkSocialUI(); + activateOneProvider(gProviders[0], true, function() { + info("second activation completed"); + is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded"); + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => { + + // after closing the addons tab, verify provider is still installed + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + is(aAddons.length, 1, "there can be only one"); + + let doc = addonsTab.linkedBrowser.contentDocument; + let list = doc.getElementById("addon-list"); + is(list.childNodes.length, 1, "only one addon is displayed"); + + BrowserTestUtils.removeTab(addonsTab).then(next); + }); + }); + }); + }); + }); + }); + }); + }, true); + } +} diff --git a/browser/base/content/test/social/head.js b/browser/base/content/test/social/head.js new file mode 100644 index 000000000..ea175c97a --- /dev/null +++ b/browser/base/content/test/social/head.js @@ -0,0 +1,273 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +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"); + + +function promiseObserverNotified(aTopic) { + return new Promise(resolve => { + Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) { + dump("notification promised "+aTopic); + Services.obs.removeObserver(onNotification, aTopic); + TestUtils.executeSoon(() => resolve({subject: aSubject, data: aData})); + }, aTopic, false); + }); +} + +// Check that a specified (string) URL hasn't been "remembered" (ie, is not +// in history, will not appear in about:newtab or auto-complete, etc.) +function promiseSocialUrlNotRemembered(url) { + return new Promise(resolve => { + let uri = Services.io.newURI(url, null, null); + PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) { + ok(!aIsVisited, "social URL " + url + " should not be in global history"); + resolve(); + }); + }); +} + +var gURLsNotRemembered = []; + + +function checkProviderPrefsEmpty(isError) { + let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); + let prefs = MANIFEST_PREFS.getChildList("", []); + let c = 0; + for (let pref of prefs) { + if (MANIFEST_PREFS.prefHasUserValue(pref)) { + info("provider [" + pref + "] manifest left installed from previous test"); + c++; + } + } + is(c, 0, "all provider prefs uninstalled from previous test"); + is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length); +} + +function defaultFinishChecks() { + checkProviderPrefsEmpty(true); + finish(); +} + +function runSocialTestWithProvider(manifest, callback, finishcallback) { + + let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + + let manifests = Array.isArray(manifest) ? manifest : [manifest]; + + // Check that none of the provider's content ends up in history. + function* finishCleanUp() { + for (let i = 0; i < manifests.length; i++) { + let m = manifests[i]; + for (let what of ['iconURL', 'shareURL']) { + if (m[what]) { + yield promiseSocialUrlNotRemembered(m[what]); + } + } + } + for (let i = 0; i < gURLsNotRemembered.length; i++) { + yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]); + } + gURLsNotRemembered = []; + } + + info("runSocialTestWithProvider: " + manifests.toSource()); + + let finishCount = 0; + function finishIfDone(callFinish) { + finishCount++; + if (finishCount == manifests.length) + Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks); + } + function removeAddedProviders(cleanup) { + manifests.forEach(function (m) { + // If we're "cleaning up", don't call finish when done. + let callback = cleanup ? function () {} : finishIfDone; + // Similarly, if we're cleaning up, catch exceptions from removeProvider + let removeProvider = SocialService.disableProvider.bind(SocialService); + if (cleanup) { + removeProvider = function (origin, cb) { + try { + SocialService.disableProvider(origin, cb); + } catch (ex) { + // Ignore "provider doesn't exist" errors. + if (ex.message.indexOf("SocialService.disableProvider: no provider with origin") == 0) + return; + info("Failed to clean up provider " + origin + ": " + ex); + } + } + } + removeProvider(m.origin, callback); + }); + } + function finishSocialTest(cleanup) { + removeAddedProviders(cleanup); + } + + let providersAdded = 0; + + manifests.forEach(function (m) { + SocialService.addProvider(m, function(provider) { + + providersAdded++; + info("runSocialTestWithProvider: provider added"); + + // we want to set the first specified provider as the UI's provider + if (provider.origin == manifests[0].origin) { + firstProvider = provider; + } + + // If we've added all the providers we need, call the callback to start + // the tests (and give it a callback it can call to finish them) + if (providersAdded == manifests.length) { + registerCleanupFunction(function () { + finishSocialTest(true); + }); + BrowserTestUtils.waitForCondition(() => provider.enabled, + "providers added and enabled").then(() => { + info("provider has been enabled"); + callback(finishSocialTest); + }); + } + }); + }); +} + +function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) { + let testIter = (function*() { + for (let name in tests) { + if (tests.hasOwnProperty(name)) { + yield [name, tests[name]]; + } + } + })(); + let providersAtStart = Social.providers.length; + info("runSocialTests: start test run with " + providersAtStart + " providers"); + window.focus(); + + + if (cbPreTest === undefined) { + cbPreTest = function(cb) { cb() }; + } + if (cbPostTest === undefined) { + cbPostTest = function(cb) { cb() }; + } + + function runNextTest() { + let result = testIter.next(); + if (result.done) { + // out of items: + (cbFinish || defaultFinishChecks)(); + is(providersAtStart, Social.providers.length, + "runSocialTests: finish test run with " + Social.providers.length + " providers"); + return; + } + let [name, func] = result.value; + // We run on a timeout to help keep the debug messages sane. + executeSoon(function() { + function cleanupAndRunNextTest() { + info("sub-test " + name + " complete"); + cbPostTest(runNextTest); + } + cbPreTest(function() { + info("pre-test: starting with " + Social.providers.length + " providers"); + info("sub-test " + name + " starting"); + try { + func.call(tests, cleanupAndRunNextTest); + } catch (ex) { + ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack); + cleanupAndRunNextTest(); + } + }) + }); + } + runNextTest(); +} + +// A fairly large hammer which checks all aspects of the SocialUI for +// internal consistency. +function checkSocialUI(win) { + let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + // if we have enabled providers, we should also have instances of those + // providers + if (SocialService.hasEnabledProviders) { + ok(Social.providers.length > 0, "providers are enabled"); + } else { + is(Social.providers.length, 0, "providers are not enabled"); + } +} + +function setManifestPref(name, manifest) { + let string = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + string.data = JSON.stringify(manifest); + Services.prefs.setComplexValue(name, Ci.nsISupportsString, string); +} + +function getManifestPrefname(aManifest) { + // is same as the generated name in SocialServiceInternal.getManifestPrefname + let originUri = Services.io.newURI(aManifest.origin, null, null); + return "social.manifest." + originUri.hostPort.replace('.', '-'); +} + +function ensureFrameLoaded(frame, uri) { + return new Promise(resolve => { + if (frame.contentDocument && frame.contentDocument.readyState == "complete" && + (!uri || frame.contentDocument.location.href == uri)) { + resolve(); + } else { + frame.addEventListener("load", function handler() { + if (uri && frame.contentDocument.location.href != uri) + return; + frame.removeEventListener("load", handler, true); + resolve() + }, true); + } + }); +} + +// Support for going on and offline. +// (via browser/base/content/test/browser_bookmark_titles.js) +var origProxyType = Services.prefs.getIntPref('network.proxy.type'); + +function toggleOfflineStatus(goOffline) { + // Bug 968887 fix. when going on/offline, wait for notification before continuing + return new Promise(resolve => { + if (!goOffline) { + Services.prefs.setIntPref('network.proxy.type', origProxyType); + } + if (goOffline != Services.io.offline) { + info("initial offline state " + Services.io.offline); + let expect = !Services.io.offline; + Services.obs.addObserver(function offlineChange(subject, topic, data) { + Services.obs.removeObserver(offlineChange, "network:offline-status-changed"); + info("offline state changed to " + Services.io.offline); + is(expect, Services.io.offline, "network:offline-status-changed successful toggle"); + resolve(); + }, "network:offline-status-changed", false); + BrowserOffline.toggleOfflineStatus(); + } else { + resolve(); + } + if (goOffline) { + Services.prefs.setIntPref('network.proxy.type', 0); + // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache. + Services.cache2.clear(); + } + }); +} + +function goOffline() { + // Simulate a network outage with offline mode. (Localhost is still + // accessible in offline mode, so disable the test proxy as well.) + return toggleOfflineStatus(true); +} + +function goOnline(callback) { + return toggleOfflineStatus(false); +} diff --git a/browser/base/content/test/social/microformats.html b/browser/base/content/test/social/microformats.html new file mode 100644 index 000000000..1a0e4436b --- /dev/null +++ b/browser/base/content/test/social/microformats.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <body> + <head><title>Raspberry Pi Page</title></head> + <div class="hproduct"> + <h2 class="fn">Raspberry Pi</h2> + <img class="photo" src="https://example.com/someimage.jpg" /> + <p class="description">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p> + <a class="url" href="https://example.com/">More info about the Raspberry Pi</a> + <p class="price">29.95</p> + <p class="review hreview"><span id="test-review" class="rating">4.5</span> out of 5</p> + <p>Categories: + <a rel="tag" href="https://example.com/wiki/computer" class="category">Computer</a>, + <a rel="tag" href="https://example.com/wiki/education" class="category">Education</a> + </p> + </div> + </body> +</html> diff --git a/browser/base/content/test/social/moz.png b/browser/base/content/test/social/moz.png Binary files differnew file mode 100644 index 000000000..769c63634 --- /dev/null +++ b/browser/base/content/test/social/moz.png diff --git a/browser/base/content/test/social/opengraph/og_invalid_url.html b/browser/base/content/test/social/opengraph/og_invalid_url.html new file mode 100644 index 000000000..ad1dae2be --- /dev/null +++ b/browser/base/content/test/social/opengraph/og_invalid_url.html @@ -0,0 +1,11 @@ +<html xmlns:og="http://ogp.me/ns#"> +<head> + <meta property="og:url" content="chrome://browser/content/aboutDialog.xul"/> + <meta property="og:site_name" content="Evil chrome delivering website"/> + <meta property="og:description" + content="A test corpus file for open graph tags passing a bad url"/> +</head> +<body> + Open Graph Test Page +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/opengraph.html b/browser/base/content/test/social/opengraph/opengraph.html new file mode 100644 index 000000000..50b7703b8 --- /dev/null +++ b/browser/base/content/test/social/opengraph/opengraph.html @@ -0,0 +1,13 @@ +<html xmlns:og="http://ogp.me/ns#"> +<head> + <meta property="og:title" content=">This is my title<"/> + <meta property="og:url" content="https://www.mozilla.org"/> + <meta property="og:image" content="https://www.mozilla.org/favicon.png"/> + <meta property="og:site_name" content=">My simple test page<"/> + <meta property="og:description" + content="A test corpus file for open graph tags we care about"/> +</head> +<body> + Open Graph Test Page +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/shortlink_linkrel.html b/browser/base/content/test/social/opengraph/shortlink_linkrel.html new file mode 100644 index 000000000..54c40c376 --- /dev/null +++ b/browser/base/content/test/social/opengraph/shortlink_linkrel.html @@ -0,0 +1,10 @@ +<html> +<head> + <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" /> + <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" /> + <link rel="shortlink" href="http://imshort/p/abcde" /> +</head> +<body> + link[rel='shortlink'] +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/shorturl_link.html b/browser/base/content/test/social/opengraph/shorturl_link.html new file mode 100644 index 000000000..667122cea --- /dev/null +++ b/browser/base/content/test/social/opengraph/shorturl_link.html @@ -0,0 +1,10 @@ +<html> +<head> + <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" /> + <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" /> + <link id="shorturl" rev="canonical" type="text/html" href="http://imshort/p/abcde" /> +</head> +<body> + link id="shorturl" +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/shorturl_linkrel.html b/browser/base/content/test/social/opengraph/shorturl_linkrel.html new file mode 100644 index 000000000..36533528e --- /dev/null +++ b/browser/base/content/test/social/opengraph/shorturl_linkrel.html @@ -0,0 +1,25 @@ +<html> +<head> + <title>Test Image</title> + + <meta name="description" content="Iron man in a tutu" /> + <meta name="title" content="Test Image" /> + + <meta name="medium" content="image" /> + <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" /> + <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" /> + <link id="shorturl" href="http://imshort/p/abcde" /> + + <meta property="og:title" content="TestImage" /> + <meta property="og:type" content="photos:photo" /> + <meta property="og:url" content="http://www.example.com/photos/56789/" /> + <meta property="og:site_name" content="My Photo Site" /> + <meta property="og:description" content="Iron man in a tutu" /> + <meta property="og:image" content="http://example.com/1234/56789.jpg" /> + <meta property="og:image:width" content="480" /> + <meta property="og:image:height" content="640" /> +</head> +<body> + link[rel='shorturl'] +</body> +</html> diff --git a/browser/base/content/test/social/share.html b/browser/base/content/test/social/share.html new file mode 100644 index 000000000..55cba9844 --- /dev/null +++ b/browser/base/content/test/social/share.html @@ -0,0 +1,9 @@ +<html> + <head> + <meta charset="utf-8"> + </head> + <body onload="document.getElementById('testclose').focus()"> + <p>This is a test social share window.</p> + <input id="testclose"/> + </body> +</html> diff --git a/browser/base/content/test/social/share_activate.html b/browser/base/content/test/social/share_activate.html new file mode 100644 index 000000000..69707e705 --- /dev/null +++ b/browser/base/content/test/social/share_activate.html @@ -0,0 +1,35 @@ +<html> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<head> + <title>Activation test</title> +</head> +<script> + +var data = { + // currently required + "name": "Demo Social Service", + // browser_share.js serves this page from "https://example.com" + "origin": "https://example.com", + "iconURL": "chrome://branding/content/icon16.png", + "icon32URL": "chrome://branding/content/favicon32.png", + "icon64URL": "chrome://branding/content/icon64.png", + "shareURL": "/browser/browser/base/content/test/social/share.html" +} + +function activate(node) { + node.setAttribute("data-service", JSON.stringify(data)); + var event = new CustomEvent("ActivateSocialFeature"); + node.dispatchEvent(event); +} + +</script> +<body> + +nothing to see here + +<button id="activation" onclick="activate(this, true)">Activate the share provider</button> + +</body> +</html> diff --git a/browser/base/content/test/social/social_activate.html b/browser/base/content/test/social/social_activate.html new file mode 100644 index 000000000..78da597a1 --- /dev/null +++ b/browser/base/content/test/social/social_activate.html @@ -0,0 +1,41 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Activation test</title> +</head> +<script> +// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun +var data = { + // currently required + "name": "Demo Social Service", + "iconURL": "chrome://branding/content/icon16.png", + "icon32URL": "chrome://branding/content/favicon32.png", + "icon64URL": "chrome://branding/content/icon64.png", + + // at least one of these must be defined + "shareURL": "/browser/browser/base/content/test/social/social_share.html", + "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html", + + // should be available for display purposes + "description": "A short paragraph about this provider", + "author": "Shane Caraveo, Mozilla", + + // optional + "version": "1.0" +} + +function activate(node) { + node.setAttribute("data-service", JSON.stringify(data)); + var event = new CustomEvent("ActivateSocialFeature"); + node.dispatchEvent(event); +} + +</script> +<body> + +nothing to see here + +<button id="activation" onclick="activate(this)">Activate The Demo Provider</button> + +</body> +</html> diff --git a/browser/base/content/test/social/social_activate_basic.html b/browser/base/content/test/social/social_activate_basic.html new file mode 100644 index 000000000..78da597a1 --- /dev/null +++ b/browser/base/content/test/social/social_activate_basic.html @@ -0,0 +1,41 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Activation test</title> +</head> +<script> +// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun +var data = { + // currently required + "name": "Demo Social Service", + "iconURL": "chrome://branding/content/icon16.png", + "icon32URL": "chrome://branding/content/favicon32.png", + "icon64URL": "chrome://branding/content/icon64.png", + + // at least one of these must be defined + "shareURL": "/browser/browser/base/content/test/social/social_share.html", + "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html", + + // should be available for display purposes + "description": "A short paragraph about this provider", + "author": "Shane Caraveo, Mozilla", + + // optional + "version": "1.0" +} + +function activate(node) { + node.setAttribute("data-service", JSON.stringify(data)); + var event = new CustomEvent("ActivateSocialFeature"); + node.dispatchEvent(event); +} + +</script> +<body> + +nothing to see here + +<button id="activation" onclick="activate(this)">Activate The Demo Provider</button> + +</body> +</html> diff --git a/browser/base/content/test/social/social_activate_iframe.html b/browser/base/content/test/social/social_activate_iframe.html new file mode 100644 index 000000000..bde884c9d --- /dev/null +++ b/browser/base/content/test/social/social_activate_iframe.html @@ -0,0 +1,11 @@ +<html> +<head> + <title>Activation iframe test</title> +</head> + +<body> + +<iframe src="social_activate_basic.html"/> + +</body> +</html> diff --git a/browser/base/content/test/social/social_crash_content_helper.js b/browser/base/content/test/social/social_crash_content_helper.js new file mode 100644 index 000000000..4698b6957 --- /dev/null +++ b/browser/base/content/test/social/social_crash_content_helper.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. +* http://creativecommons.org/publicdomain/zero/1.0/ */ + +var Cu = Components.utils; + +// Ideally we would use CrashTestUtils.jsm, but that's only available for +// xpcshell tests - so we just copy a ctypes crasher from it. +Cu.import("resource://gre/modules/ctypes.jsm"); +var crash = function() { // this will crash when called. + let zero = new ctypes.intptr_t(8); + let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + badptr.contents +}; + + +var TestHelper = { + init: function() { + addMessageListener("social-test:crash", this); + }, + + receiveMessage: function(msg) { + switch (msg.name) { + case "social-test:crash": + privateNoteIntentionalCrash(); + crash(); + break; + } + }, +} + +TestHelper.init(); diff --git a/browser/base/content/test/social/social_postActivation.html b/browser/base/content/test/social/social_postActivation.html new file mode 100644 index 000000000..e0a6acfdf --- /dev/null +++ b/browser/base/content/test/social/social_postActivation.html @@ -0,0 +1,12 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Post-Activation test</title> +</head> + +<body> + +Post Activation landing page + +</body> +</html> diff --git a/browser/base/content/test/tabPrompts/.eslintrc.js b/browser/base/content/test/tabPrompts/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/tabPrompts/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/tabPrompts/browser.ini b/browser/base/content/test/tabPrompts/browser.ini new file mode 100644 index 000000000..9b94f14c5 --- /dev/null +++ b/browser/base/content/test/tabPrompts/browser.ini @@ -0,0 +1,4 @@ +[browser_closeTabSpecificPanels.js] +[browser_multiplePrompts.js] +[browser_openPromptInBackgroundTab.js] +support-files = openPromptOffTimeout.html diff --git a/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js new file mode 100644 index 000000000..30c15a56f --- /dev/null +++ b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js @@ -0,0 +1,41 @@ +"use strict"; + +/* + * This test creates multiple panels, one that has been tagged as specific to its tab's content + * and one that isn't. When a tab loses focus, panel specific to that tab should close. + * The non-specific panel should remain open. + * + */ + +add_task(function*() { + let tab1 = gBrowser.addTab("http://mochi.test:8888/#0"); + let tab2 = gBrowser.addTab("http://mochi.test:8888/#1"); + let specificPanel = document.createElement("panel"); + specificPanel.setAttribute("tabspecific", "true"); + let generalPanel = document.createElement("panel"); + let anchor = document.getElementById(CustomizableUI.AREA_NAVBAR); + + anchor.appendChild(specificPanel); + anchor.appendChild(generalPanel); + is(specificPanel.state, "closed", "specificPanel starts as closed"); + is(generalPanel.state, "closed", "generalPanel starts as closed"); + + let specificPanelPromise = BrowserTestUtils.waitForEvent(specificPanel, "popupshown"); + specificPanel.openPopupAtScreen(210, 210); + yield specificPanelPromise; + is(specificPanel.state, "open", "specificPanel has been opened"); + + let generalPanelPromise = BrowserTestUtils.waitForEvent(generalPanel, "popupshown"); + generalPanel.openPopupAtScreen(510, 510); + yield generalPanelPromise; + is(generalPanel.state, "open", "generalPanel has been opened"); + + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(specificPanel.state, "closed", "specificPanel panel is closed after its tab loses focus"); + is(generalPanel.state, "open", "generalPanel is still open after tab switch"); + + specificPanel.remove(); + generalPanel.remove(); + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); +}); diff --git a/browser/base/content/test/tabPrompts/browser_multiplePrompts.js b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js new file mode 100644 index 000000000..c548429ea --- /dev/null +++ b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js @@ -0,0 +1,72 @@ +"use strict"; + +/* + * This test triggers multiple alerts on one single tab, because it"s possible + * for web content to do so. The behavior is described in bug 1266353. + * + * We assert the presentation of the multiple alerts, ensuring we show only + * the oldest one. + */ +add_task(function*() { + const PROMPTCOUNT = 5; + + let contentScript = function() { + var i = 5; // contentScript has no access to PROMPTCOUNT. + window.addEventListener("message", function() { + i--; + if (i) { + window.postMessage("ping", "*"); + } + alert("Alert countdown #" + i); + }); + window.postMessage("ping", "*"); + }; + let url = "data:text/html,<script>(" + encodeURIComponent(contentScript.toSource()) + ")();</script>" + + let promptsOpenedPromise = new Promise(function(resolve) { + let unopenedPromptCount = PROMPTCOUNT; + Services.obs.addObserver(function observer() { + unopenedPromptCount--; + if (!unopenedPromptCount) { + Services.obs.removeObserver(observer, "tabmodal-dialog-loaded"); + info("Prompts opened."); + resolve(); + } + }, "tabmodal-dialog-loaded", false); + }); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true); + info("Tab loaded"); + + yield promptsOpenedPromise; + + let promptsCount = PROMPTCOUNT; + while (promptsCount--) { + let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt"); + is(prompts.length, promptsCount + 1, "There should be " + (promptsCount + 1) + " prompt(s)."); + // The oldest should be the first. + let i = 0; + for (let prompt of prompts) { + is(prompt.Dialog.args.text, "Alert countdown #" + i, "The #" + i + " alert should be labelled as such."); + if (i !== promptsCount) { + is(prompt.hidden, true, "This prompt should be hidden."); + i++; + continue; + } + + is(prompt.hidden, false, "The last prompt should not be hidden."); + prompt.onButtonClick(0); + + // The click is handled async; wait for an event loop turn for that to + // happen. + yield new Promise(function(resolve) { + Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL); + }); + } + } + + let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt"); + is(prompts.length, 0, "Prompts should all be dismissed."); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js new file mode 100644 index 000000000..d244d157a --- /dev/null +++ b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js @@ -0,0 +1,66 @@ +"use strict"; + +const ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://example.com/"); +let pageWithAlert = ROOT + "openPromptOffTimeout.html"; + +registerCleanupFunction(function() { + Services.perms.removeAll(makeURI(pageWithAlert)); +}); + +/* + * This test opens a tab that alerts when it is hidden. We then switch away + * from the tab, and check that by default the tab is not automatically + * re-selected. We also check that a checkbox appears in the alert that allows + * the user to enable this automatically re-selecting. We then check that + * checking the checkbox does actually enable that behaviour. + */ +add_task(function*() { + yield SpecialPowers.pushPrefEnv({"set": [["browser.tabs.dontfocusfordialogs", true]]}); + let firstTab = gBrowser.selectedTab; + // load page that opens prompt when page is hidden + let openedTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageWithAlert, true); + let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute("attention", openedTab, "true"); + // switch away from that tab again - this triggers the alert. + yield BrowserTestUtils.switchTab(gBrowser, firstTab); + // ... but that's async on e10s... + yield openedTabGotAttentionPromise; + // check for attention attribute + is(openedTab.getAttribute("attention"), "true", "Tab with alert should have 'attention' attribute."); + ok(!openedTab.selected, "Tab with alert should not be selected"); + + // switch tab back, and check the checkbox is displayed: + yield BrowserTestUtils.switchTab(gBrowser, openedTab); + // check the prompt is there, and the extra row is present + let prompts = openedTab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt"); + is(prompts.length, 1, "There should be 1 prompt"); + let ourPrompt = prompts[0]; + let row = ourPrompt.querySelector("row"); + ok(row, "Should have found the row with our checkbox"); + let checkbox = row.querySelector("checkbox[label*='example.com']"); + ok(checkbox, "The checkbox should be there"); + ok(!checkbox.checked, "Checkbox shouldn't be checked"); + // tick box and accept dialog + checkbox.checked = true; + ourPrompt.onButtonClick(0); + // Wait for that click to actually be handled completely. + yield new Promise(function(resolve) { + Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL); + }); + // check permission is set + let ps = Services.perms; + is(ps.ALLOW_ACTION, ps.testPermission(makeURI(pageWithAlert), "focus-tab-by-prompt"), + "Tab switching should now be allowed"); + + let openedTabSelectedPromise = BrowserTestUtils.waitForAttribute("selected", openedTab, "true"); + // switch to other tab again + yield BrowserTestUtils.switchTab(gBrowser, firstTab); + + // This is sync in non-e10s, but in e10s we need to wait for this, so yield anyway. + // Note that the switchTab promise doesn't actually guarantee anything about *which* + // tab ends up as selected when its event fires, so using that here wouldn't work. + yield openedTabSelectedPromise; + // should be switched back + ok(openedTab.selected, "Ta-dah, the other tab should now be selected again!"); + + yield BrowserTestUtils.removeTab(openedTab); +}); diff --git a/browser/base/content/test/tabPrompts/openPromptOffTimeout.html b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html new file mode 100644 index 000000000..e865c7872 --- /dev/null +++ b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html @@ -0,0 +1,10 @@ +<body> +This page opens an alert box when the page is hidden. +<script> +document.addEventListener("visibilitychange", () => { + if (document.hidden) { + alert("You hid my page!"); + } +}, false); +</script> +</body> diff --git a/browser/base/content/test/tabcrashed/browser.ini b/browser/base/content/test/tabcrashed/browser.ini new file mode 100644 index 000000000..051b40d9f --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser.ini @@ -0,0 +1,13 @@ +[DEFAULT] +support-files = + head.js +[browser_shown.js] +skip-if = !e10s || !crashreporter +[browser_clearEmail.js] +skip-if = !e10s || !crashreporter +[browser_showForm.js] +skip-if = !e10s || !crashreporter +[browser_withoutDump.js] +skip-if = !e10s +[browser_autoSubmitRequest.js] +skip-if = !e10s || !crashreporter diff --git a/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js new file mode 100644 index 000000000..778331814 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js @@ -0,0 +1,152 @@ +"use strict"; + +const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; +const AUTOSUBMIT_PREF = "browser.crashReports.unsubmittedCheck.autoSubmit2"; + +const {TabStateFlusher} = + Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + +// On debug builds, crashing tabs results in much thinking, which +// slows down the test and results in intermittent test timeouts, +// so we'll pump up the expected timeout for this test. +requestLongerTimeout(2); + +/** + * Tests that if the user is not configured to autosubmit + * backlogged crash reports, that we offer to do that, and + * that the user can accept that offer. + */ +add_task(function* test_show_form() { + yield SpecialPowers.pushPrefEnv({ + set: [[AUTOSUBMIT_PREF, false]], + }) + + return BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + // Make sure we've flushed the browser messages so that + // we can restore it. + yield TabStateFlusher.flush(browser); + + // Now crash the browser. + yield BrowserTestUtils.crashBrowser(browser); + + let doc = browser.contentDocument; + + // Ensure the request is visible. We can safely reach into + // the content since about:tabcrashed is an in-process URL. + let requestAutoSubmit = doc.getElementById("requestAutoSubmit"); + Assert.ok(!requestAutoSubmit.hidden, + "Request for autosubmission is visible."); + + // Since the pref is set to false, the checkbox should be + // unchecked. + let autoSubmit = doc.getElementById("autoSubmit"); + Assert.ok(!autoSubmit.checked, + "Checkbox for autosubmission is not checked.") + + // Check the checkbox, and then restore the tab. + autoSubmit.checked = true; + let restoreButton = doc.getElementById("restoreTab"); + restoreButton.click(); + + yield BrowserTestUtils.browserLoaded(browser, false, PAGE); + + // The autosubmission pref should now be set. + Assert.ok(Services.prefs.getBoolPref(AUTOSUBMIT_PREF), + "Autosubmission pref should have been set."); + }); +}); + +/** + * Tests that if the user is autosubmitting backlogged crash reports + * that we don't make the offer again. + */ +add_task(function* test_show_form() { + yield SpecialPowers.pushPrefEnv({ + set: [[AUTOSUBMIT_PREF, true]], + }) + + return BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + yield TabStateFlusher.flush(browser); + // Now crash the browser. + yield BrowserTestUtils.crashBrowser(browser); + + let doc = browser.contentDocument; + + // Ensure the request is NOT visible. We can safely reach into + // the content since about:tabcrashed is an in-process URL. + let requestAutoSubmit = doc.getElementById("requestAutoSubmit"); + Assert.ok(requestAutoSubmit.hidden, + "Request for autosubmission is not visible."); + + // Restore the tab. + let restoreButton = doc.getElementById("restoreTab"); + restoreButton.click(); + + yield BrowserTestUtils.browserLoaded(browser, false, PAGE); + + // The autosubmission pref should still be set to true. + Assert.ok(Services.prefs.getBoolPref(AUTOSUBMIT_PREF), + "Autosubmission pref should have been set."); + }); +}); + +/** + * Tests that we properly set the autoSubmit preference if the user is + * presented with a tabcrashed page without a crash report. + */ +add_task(function* test_no_offer() { + // We should default to sending the report. + Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport")); + + yield SpecialPowers.pushPrefEnv({ + set: [[AUTOSUBMIT_PREF, false]], + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + yield TabStateFlusher.flush(browser); + + // Make it so that it seems like no dump is available for the next crash. + prepareNoDump(); + + // Now crash the browser. + yield BrowserTestUtils.crashBrowser(browser); + + // eslint-disable-next-line mozilla/no-cpows-in-tests + let doc = browser.contentDocument; + + // Ensure the request to autosubmit is invisible, since there's no report. + let requestRect = doc.getElementById("requestAutoSubmit") + .getBoundingClientRect(); + Assert.equal(0, requestRect.height, + "Request for autosubmission has no height"); + Assert.equal(0, requestRect.width, + "Request for autosubmission has no width"); + + // Since the pref is set to false, the checkbox should be + // unchecked. + let autoSubmit = doc.getElementById("autoSubmit"); + Assert.ok(!autoSubmit.checked, + "Checkbox for autosubmission is not checked."); + + let restoreButton = doc.getElementById("restoreTab"); + restoreButton.click(); + + yield BrowserTestUtils.browserLoaded(browser, false, PAGE); + + // The autosubmission pref should now be set. + Assert.ok(!Services.prefs.getBoolPref(AUTOSUBMIT_PREF), + "Autosubmission pref should not have changed."); + }); + + // We should not have changed the default value for sending the report. + Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport")); +}); diff --git a/browser/base/content/test/tabcrashed/browser_clearEmail.js b/browser/base/content/test/tabcrashed/browser_clearEmail.js new file mode 100644 index 000000000..9ec04944f --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_clearEmail.js @@ -0,0 +1,85 @@ +"use strict"; + +const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; +const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; +const EMAIL = "foo@privacy.com"; + +/** + * Sets up the browser to send crash reports to the local crash report + * testing server. + */ +add_task(function* setup() { + // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash + // reports. This test needs them enabled. The test also needs a mock + // report server, and fortunately one is already set up by toolkit/ + // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL, + // which CrashSubmit.jsm uses as a server override. + let env = Cc["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT"); + let serverUrl = env.get("MOZ_CRASHREPORTER_URL"); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + env.set("MOZ_CRASHREPORTER_URL", SERVER_URL); + + // By default, requesting the email address of the user is disabled. + // For the purposes of this test, we turn it back on. + yield SpecialPowers.pushPrefEnv({ + set: [["browser.tabs.crashReporting.requestEmail", true]], + }); + + registerCleanupFunction(function() { + env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); + env.set("MOZ_CRASHREPORTER_URL", serverUrl); + }); +}); + +/** + * Test that if we have an email address stored in prefs, and we decide + * not to submit the email address in the next crash report, that we + * clear the email address. + */ +add_task(function* test_clear_email() { + return BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + let prefs = TabCrashHandler.prefs; + let originalSendReport = prefs.getBoolPref("sendReport"); + let originalEmailMe = prefs.getBoolPref("emailMe"); + let originalIncludeURL = prefs.getBoolPref("includeURL"); + let originalEmail = prefs.getCharPref("email"); + + // Pretend that we stored an email address from the previous + // crash + prefs.setCharPref("email", EMAIL); + prefs.setBoolPref("emailMe", true); + + let tab = gBrowser.getTabForBrowser(browser); + yield BrowserTestUtils.crashBrowser(browser); + let doc = browser.contentDocument; + + // Since about:tabcrashed will run in the parent process, we can safely + // manipulate its DOM nodes directly + let emailMe = doc.getElementById("emailMe"); + emailMe.checked = false; + + let crashReport = promiseCrashReport({ + Email: "", + }); + + let restoreTab = browser.contentDocument.getElementById("restoreTab"); + restoreTab.click(); + yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored"); + yield crashReport; + + is(prefs.getCharPref("email"), "", "No email address should be stored"); + + // Submitting the crash report may have set some prefs regarding how to + // send tab crash reports. Let's reset them for the next test. + prefs.setBoolPref("sendReport", originalSendReport); + prefs.setBoolPref("emailMe", originalEmailMe); + prefs.setBoolPref("includeURL", originalIncludeURL); + prefs.setCharPref("email", originalEmail); + }); +}); + diff --git a/browser/base/content/test/tabcrashed/browser_showForm.js b/browser/base/content/test/tabcrashed/browser_showForm.js new file mode 100644 index 000000000..780af93fb --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_showForm.js @@ -0,0 +1,40 @@ +"use strict"; + +const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + +// On debug builds, crashing tabs results in much thinking, which +// slows down the test and results in intermittent test timeouts, +// so we'll pump up the expected timeout for this test. +requestLongerTimeout(2); + +/** + * Tests that we show the about:tabcrashed additional details form + * if the "submit a crash report" checkbox was checked by default. + */ +add_task(function* test_show_form() { + return BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + // Flip the pref so that the checkbox should be checked + // by default. + let pref = TabCrashHandler.prefs.root + "sendReport"; + yield SpecialPowers.pushPrefEnv({ + set: [[pref, true]] + }); + + // Now crash the browser. + yield BrowserTestUtils.crashBrowser(browser); + + let doc = browser.contentDocument; + + // Ensure the checkbox is checked. We can safely reach into + // the content since about:tabcrashed is an in-process URL. + let checkbox = doc.getElementById("sendReport"); + ok(checkbox.checked, "Send report checkbox is checked."); + + // Ensure the options form is displayed. + let options = doc.getElementById("options"); + ok(!options.hidden, "Showing the crash report options form."); + }); +}); diff --git a/browser/base/content/test/tabcrashed/browser_shown.js b/browser/base/content/test/tabcrashed/browser_shown.js new file mode 100644 index 000000000..d09d9438f --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_shown.js @@ -0,0 +1,203 @@ +"use strict"; + +const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; +const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; +const COMMENTS = "Here's my test comment!"; +const EMAIL = "foo@privacy.com"; + +/** + * Sets up the browser to send crash reports to the local crash report + * testing server. + */ +add_task(function* setup() { + // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash + // reports. This test needs them enabled. The test also needs a mock + // report server, and fortunately one is already set up by toolkit/ + // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL, + // which CrashSubmit.jsm uses as a server override. + let env = Cc["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT"); + let serverUrl = env.get("MOZ_CRASHREPORTER_URL"); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + env.set("MOZ_CRASHREPORTER_URL", SERVER_URL); + + // On debug builds, crashing tabs results in much thinking, which + // slows down the test and results in intermittent test timeouts, + // so we'll pump up the expected timeout for this test. + requestLongerTimeout(2); + + registerCleanupFunction(function() { + env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); + env.set("MOZ_CRASHREPORTER_URL", serverUrl); + }); +}); + +/** + * This function returns a Promise that resolves once the following + * actions have taken place: + * + * 1) A new tab is opened up at PAGE + * 2) The tab is crashed + * 3) The about:tabcrashed page's fields are set in accordance with + * fieldValues + * 4) The tab is restored + * 5) A crash report is received from the testing server + * 6) Any tab crash prefs that were overwritten are reset + * + * @param fieldValues + * An Object describing how to set the about:tabcrashed + * fields. The following properties are accepted: + * + * comments (String) + * The comments to put in the comment textarea + * email (String) + * The email address to put in the email address input + * emailMe (bool) + * The checked value of the "Email me" checkbox + * includeURL (bool) + * The checked value of the "Include URL" checkbox + * + * If any of these fields are missing, the defaults from + * the user preferences are used. + * @param expectedExtra + * An Object describing the expected values that the submitted + * crash report's extra data should contain. + * @returns Promise + */ +function crashTabTestHelper(fieldValues, expectedExtra) { + return BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + let prefs = TabCrashHandler.prefs; + let originalSendReport = prefs.getBoolPref("sendReport"); + let originalEmailMe = prefs.getBoolPref("emailMe"); + let originalIncludeURL = prefs.getBoolPref("includeURL"); + let originalEmail = prefs.getCharPref("email"); + + let tab = gBrowser.getTabForBrowser(browser); + yield BrowserTestUtils.crashBrowser(browser); + let doc = browser.contentDocument; + + // Since about:tabcrashed will run in the parent process, we can safely + // manipulate its DOM nodes directly + let comments = doc.getElementById("comments"); + let email = doc.getElementById("email"); + let emailMe = doc.getElementById("emailMe"); + let includeURL = doc.getElementById("includeURL"); + + if (fieldValues.hasOwnProperty("comments")) { + comments.value = fieldValues.comments; + } + + if (fieldValues.hasOwnProperty("email")) { + email.value = fieldValues.email; + } + + if (fieldValues.hasOwnProperty("emailMe")) { + emailMe.checked = fieldValues.emailMe; + } + + if (fieldValues.hasOwnProperty("includeURL")) { + includeURL.checked = fieldValues.includeURL; + } + + let crashReport = promiseCrashReport(expectedExtra); + let restoreTab = browser.contentDocument.getElementById("restoreTab"); + restoreTab.click(); + yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored"); + yield crashReport; + + // Submitting the crash report may have set some prefs regarding how to + // send tab crash reports. Let's reset them for the next test. + prefs.setBoolPref("sendReport", originalSendReport); + prefs.setBoolPref("emailMe", originalEmailMe); + prefs.setBoolPref("includeURL", originalIncludeURL); + prefs.setCharPref("email", originalEmail); + }); +} + +/** + * Tests what we send with the crash report by default. By default, we do not + * send any comments, the URL of the crashing page, or the email address of + * the user. + */ +add_task(function* test_default() { + yield crashTabTestHelper({}, { + "Comments": null, + "URL": "", + "Email": null, + }); +}); + +/** + * Test just sending a comment. + */ +add_task(function* test_just_a_comment() { + yield crashTabTestHelper({ + comments: COMMENTS, + }, { + "Comments": COMMENTS, + "URL": "", + "Email": null, + }); +}); + +/** + * Test that we don't send email if emailMe is unchecked + */ +add_task(function* test_no_email() { + yield crashTabTestHelper({ + email: EMAIL, + emailMe: false, + }, { + "Comments": null, + "URL": "", + "Email": null, + }); +}); + +/** + * Test that we can send an email address if emailMe is checked + */ +add_task(function* test_yes_email() { + yield crashTabTestHelper({ + email: EMAIL, + emailMe: true, + }, { + "Comments": null, + "URL": "", + "Email": EMAIL, + }); +}); + +/** + * Test that we will send the URL of the page if includeURL is checked. + */ +add_task(function* test_send_URL() { + yield crashTabTestHelper({ + includeURL: true, + }, { + "Comments": null, + "URL": PAGE, + "Email": null, + }); +}); + +/** + * Test that we can send comments, the email address, and the URL + */ +add_task(function* test_send_all() { + yield crashTabTestHelper({ + includeURL: true, + emailMe: true, + email: EMAIL, + comments: COMMENTS, + }, { + "Comments": COMMENTS, + "URL": PAGE, + "Email": EMAIL, + }); +}); + diff --git a/browser/base/content/test/tabcrashed/browser_withoutDump.js b/browser/base/content/test/tabcrashed/browser_withoutDump.js new file mode 100644 index 000000000..62557f443 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_withoutDump.js @@ -0,0 +1,36 @@ +"use strict"; + +const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + +add_task(function* setup() { + prepareNoDump(); +}); + +/** + * Tests tab crash page when a dump is not available. + */ +add_task(function* test_without_dump() { + return BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + let tab = gBrowser.getTabForBrowser(browser); + yield BrowserTestUtils.crashBrowser(browser); + + let tabRemovedPromise = BrowserTestUtils.removeTab(tab, { dontRemove: true }); + + yield ContentTask.spawn(browser, null, function*() { + let doc = content.document; + Assert.ok(!doc.documentElement.classList.contains("crashDumpAvailable"), + "doesn't have crash dump"); + + let options = doc.getElementById("options"); + Assert.ok(options, "has crash report options"); + Assert.ok(options.hidden, "crash report options are hidden"); + + doc.getElementById("closeTab").click(); + }); + + yield tabRemovedPromise; + }); +}); diff --git a/browser/base/content/test/tabcrashed/head.js b/browser/base/content/test/tabcrashed/head.js new file mode 100644 index 000000000..6eee08f13 --- /dev/null +++ b/browser/base/content/test/tabcrashed/head.js @@ -0,0 +1,110 @@ +/** + * Returns a Promise that resolves once a crash report has + * been submitted. This function will also test the crash + * reports extra data to see if it matches expectedExtra. + * + * @param expectedExtra (object) + * An Object whose key-value pairs will be compared + * against the key-value pairs in the extra data of the + * crash report. A test failure will occur if there is + * a mismatch. + * + * If the value of the key-value pair is "null", this will + * be interpreted as "this key should not be included in the + * extra data", and will cause a test failure if it is detected + * in the crash report. + * + * Note that this will ignore any keys that are not included + * in expectedExtra. It's possible that the crash report + * will contain other extra information that is not + * compared against. + * @returns Promise + */ +function promiseCrashReport(expectedExtra={}) { + return Task.spawn(function*() { + info("Starting wait on crash-report-status"); + let [subject, ] = + yield TestUtils.topicObserved("crash-report-status", (unused, data) => { + return data == "success"; + }); + info("Topic observed!"); + + if (!(subject instanceof Ci.nsIPropertyBag2)) { + throw new Error("Subject was not a Ci.nsIPropertyBag2"); + } + + let remoteID = getPropertyBagValue(subject, "serverCrashID"); + if (!remoteID) { + throw new Error("Report should have a server ID"); + } + + let file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + file.initWithPath(Services.crashmanager._submittedDumpsDir); + file.append(remoteID + ".txt"); + if (!file.exists()) { + throw new Error("Report should have been received by the server"); + } + + file.remove(false); + + let extra = getPropertyBagValue(subject, "extra"); + if (!(extra instanceof Ci.nsIPropertyBag2)) { + throw new Error("extra was not a Ci.nsIPropertyBag2"); + } + + info("Iterating crash report extra keys"); + let enumerator = extra.enumerator; + while (enumerator.hasMoreElements()) { + let key = enumerator.getNext().QueryInterface(Ci.nsIProperty).name; + let value = extra.getPropertyAsAString(key); + if (key in expectedExtra) { + if (expectedExtra[key] == null) { + ok(false, `Got unexpected key ${key} with value ${value}`); + } else { + is(value, expectedExtra[key], + `Crash report had the right extra value for ${key}`); + } + } + } + }); +} + + +/** + * For an nsIPropertyBag, returns the value for a given + * key. + * + * @param bag + * The nsIPropertyBag to retrieve the value from + * @param key + * The key that we want to get the value for from the + * bag + * @returns The value corresponding to the key from the bag, + * or null if the value could not be retrieved (for + * example, if no value is set at that key). +*/ +function getPropertyBagValue(bag, key) { + try { + let val = bag.getProperty(key); + return val; + } catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + + return null; +} + +/** + * Monkey patches TabCrashHandler.getDumpID to return null in order to test + * about:tabcrashed when a dump is not available. + */ +function prepareNoDump() { + let originalGetDumpID = TabCrashHandler.getDumpID; + TabCrashHandler.getDumpID = function(browser) { return null; }; + registerCleanupFunction(() => { + TabCrashHandler.getDumpID = originalGetDumpID; + }); +} diff --git a/browser/base/content/test/tabs/.eslintrc.js b/browser/base/content/test/tabs/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/tabs/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini new file mode 100644 index 000000000..7771e0a6e --- /dev/null +++ b/browser/base/content/test/tabs/browser.ini @@ -0,0 +1,4 @@ +[browser_tabSpinnerProbe.js] +skip-if = !e10s # Tab spinner is e10s only. +[browser_tabSwitchPrintPreview.js] +skip-if = os == 'mac' diff --git a/browser/base/content/test/tabs/browser_tabSpinnerProbe.js b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js new file mode 100644 index 000000000..c3569c2b1 --- /dev/null +++ b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js @@ -0,0 +1,93 @@ +"use strict"; + +/** + * Tests the FX_TAB_SWITCH_SPINNER_VISIBLE_MS and + * FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS telemetry probes + */ +let gMinHangTime = 500; // ms +let gMaxHangTime = 5 * 1000; // ms + +/** + * Make a data URI for a generic webpage with a script that hangs for a given + * amount of time. + * @param {?Number} aHangMs Number of milliseconds that the hang should last. + * Defaults to 0. + * @return {String} The data URI generated. + */ +function makeDataURI(aHangMs = 0) { + return `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Tab Spinner Test</title> + <script> + function hang() { + let hangDuration = ${aHangMs}; + if (hangDuration > 0) { + let startTime = window.performance.now(); + while(window.performance.now() - startTime < hangDuration) {} + } + } + </script> + </head> + <body> + <h1 id='header'>Tab Spinner Test</h1> + </body> + </html>`; +} + +/** + * Returns the sum of all values in an array. + * @param {Array} aArray An array of integers + * @return {Number} The sum of the integers in the array + */ +function sum(aArray) { + return aArray.reduce(function(previousValue, currentValue) { + return previousValue + currentValue; + }); +} + +/** + * A generator intended to be run as a Task. It tests one of the tab spinner + * telemetry probes. + * @param {String} aProbe The probe to test. Should be one of: + * - FX_TAB_SWITCH_SPINNER_VISIBLE_MS + * - FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS + */ +function* testProbe(aProbe) { + info(`Testing probe: ${aProbe}`); + let histogram = Services.telemetry.getHistogramById(aProbe); + let buckets = histogram.snapshot().ranges.filter(function(value) { + return (value > gMinHangTime && value < gMaxHangTime); + }); + let delayTime = buckets[0]; // Pick a bucket arbitrarily + + // The tab spinner does not show up instantly. We need to hang for a little + // bit of extra time to account for the tab spinner delay. + delayTime += gBrowser.selectedTab.linkedBrowser.getTabBrowser()._getSwitcher().TAB_SWITCH_TIMEOUT; + let dataURI1 = makeDataURI(delayTime); + let dataURI2 = makeDataURI(); + + let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI1); + histogram.clear(); + // Queue a hang in the content process when the + // event loop breathes next. + ContentTask.spawn(tab1.linkedBrowser, null, function*() { + content.wrappedJSObject.hang(); + }); + let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI2); + let snapshot = histogram.snapshot(); + yield BrowserTestUtils.removeTab(tab2); + yield BrowserTestUtils.removeTab(tab1); + ok(sum(snapshot.counts) > 0, + `Spinner probe should now have a value in some bucket`); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]] + }); +}); + +add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_MS")); +add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS")); diff --git a/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js new file mode 100644 index 000000000..4ec36a7cc --- /dev/null +++ b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js @@ -0,0 +1,29 @@ +const kURL1 = "data:text/html,Should I stay or should I go?"; +const kURL2 = "data:text/html,I shouldn't be here!"; + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]] + }); +}); + +/** + * Verify that if we open a new tab and try to make it the selected tab while + * print preview is up, that doesn't happen. + */ +add_task(function* () { + yield BrowserTestUtils.withNewTab(kURL1, function* (browser) { + let tab = gBrowser.addTab(kURL2); + document.getElementById("cmd_printPreview").doCommand(); + gBrowser.selectedTab = tab; + yield BrowserTestUtils.waitForCondition(() => gInPrintPreviewMode, "should be in print preview mode"); + isnot(gBrowser.selectedTab, tab, "Selected tab should not be the tab we added"); + is(gBrowser.selectedTab, PrintPreviewListener._printPreviewTab, "Selected tab should be the print preview tab"); + gBrowser.selectedTab = tab; + isnot(gBrowser.selectedTab, tab, "Selected tab should still not be the tab we added"); + is(gBrowser.selectedTab, PrintPreviewListener._printPreviewTab, "Selected tab should still be the print preview tab"); + PrintUtils.exitPrintPreview(); + yield BrowserTestUtils.waitForCondition(() => !gInPrintPreviewMode, "should be in print preview mode"); + yield BrowserTestUtils.removeTab(tab); + }); +}); diff --git a/browser/base/content/test/urlbar/.eslintrc.js b/browser/base/content/test/urlbar/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/urlbar/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/urlbar/authenticate.sjs b/browser/base/content/test/urlbar/authenticate.sjs new file mode 100644 index 000000000..58da655cf --- /dev/null +++ b/browser/base/content/test/urlbar/authenticate.sjs @@ -0,0 +1,220 @@ +function handleRequest(request, response) +{ + try { + reallyHandleRequest(request, response); + } catch (e) { + response.setStatusLine("1.0", 200, "AlmostOK"); + response.write("Error handling request: " + e); + } +} + + +function reallyHandleRequest(request, response) { + var match; + var requestAuth = true, requestProxyAuth = true; + + // Allow the caller to drive how authentication is processed via the query. + // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar + // The extra ? allows the user/pass/realm checks to succeed if the name is + // at the beginning of the query string. + var query = "?" + request.queryString; + + var expected_user = "", expected_pass = "", realm = "mochitest"; + var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy"; + var huge = false, plugin = false, anonymous = false; + var authHeaderCount = 1; + // user=xxx + match = /[^_]user=([^&]*)/.exec(query); + if (match) + expected_user = match[1]; + + // pass=xxx + match = /[^_]pass=([^&]*)/.exec(query); + if (match) + expected_pass = match[1]; + + // realm=xxx + match = /[^_]realm=([^&]*)/.exec(query); + if (match) + realm = match[1]; + + // proxy_user=xxx + match = /proxy_user=([^&]*)/.exec(query); + if (match) + proxy_expected_user = match[1]; + + // proxy_pass=xxx + match = /proxy_pass=([^&]*)/.exec(query); + if (match) + proxy_expected_pass = match[1]; + + // proxy_realm=xxx + match = /proxy_realm=([^&]*)/.exec(query); + if (match) + proxy_realm = match[1]; + + // huge=1 + match = /huge=1/.exec(query); + if (match) + huge = true; + + // plugin=1 + match = /plugin=1/.exec(query); + if (match) + plugin = true; + + // multiple=1 + match = /multiple=([^&]*)/.exec(query); + if (match) + authHeaderCount = match[1]+0; + + // anonymous=1 + match = /anonymous=1/.exec(query); + if (match) + anonymous = true; + + // Look for an authentication header, if any, in the request. + // + // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + // + // This test only supports Basic auth. The value sent by the client is + // "username:password", obscured with base64 encoding. + + var actual_user = "", actual_pass = "", authHeader, authPresent = false; + if (request.hasHeader("Authorization")) { + authPresent = true; + authHeader = request.getHeader("Authorization"); + match = /Basic (.+)/.exec(authHeader); + if (match.length != 2) + throw "Couldn't parse auth header: " + authHeader; + + var userpass = base64ToString(match[1]); // no atob() :-( + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) + throw "Couldn't decode auth header: " + userpass; + actual_user = match[1]; + actual_pass = match[2]; + } + + var proxy_actual_user = "", proxy_actual_pass = ""; + if (request.hasHeader("Proxy-Authorization")) { + authHeader = request.getHeader("Proxy-Authorization"); + match = /Basic (.+)/.exec(authHeader); + if (match.length != 2) + throw "Couldn't parse auth header: " + authHeader; + + var userpass = base64ToString(match[1]); // no atob() :-( + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) + throw "Couldn't decode auth header: " + userpass; + proxy_actual_user = match[1]; + proxy_actual_pass = match[2]; + } + + // Don't request authentication if the credentials we got were what we + // expected. + if (expected_user == actual_user && + expected_pass == actual_pass) { + requestAuth = false; + } + if (proxy_expected_user == proxy_actual_user && + proxy_expected_pass == proxy_actual_pass) { + requestProxyAuth = false; + } + + if (anonymous) { + if (authPresent) { + response.setStatusLine("1.0", 400, "Unexpected authorization header found"); + } else { + response.setStatusLine("1.0", 200, "Authorization header not found"); + } + } else { + if (requestProxyAuth) { + response.setStatusLine("1.0", 407, "Proxy authentication required"); + for (i = 0; i < authHeaderCount; ++i) + response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true); + } else if (requestAuth) { + response.setStatusLine("1.0", 401, "Authentication required"); + for (i = 0; i < authHeaderCount; ++i) + response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true); + } else { + response.setStatusLine("1.0", 200, "OK"); + } + } + + response.setHeader("Content-Type", "application/xhtml+xml", false); + response.write("<html xmlns='http://www.w3.org/1999/xhtml'>"); + response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n"); + response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n"); + response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n"); + response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n"); + response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n"); + + if (huge) { + response.write("<div style='display: none'>"); + for (i = 0; i < 100000; i++) { + response.write("123456789\n"); + } + response.write("</div>"); + response.write("<span id='footnote'>This is a footnote after the huge content fill</span>"); + } + + if (plugin) { + response.write("<embed id='embedtest' style='width: 400px; height: 100px;' " + + "type='application/x-test'></embed>\n"); + } + + response.write("</html>"); +} + + +// base64 decoder +// +// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa() +// doesn't seem to exist. :-( +/* Convert Base64 data to a string */ +const toBinaryTable = [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +]; +const base64Pad = '='; + +function base64ToString(data) { + + var result = ''; + var leftbits = 0; // number of bits decoded, but yet to be appended + var leftdata = 0; // bits decoded, but yet to be appended + + // Convert one by one. + for (var i = 0; i < data.length; i++) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data[i] == base64Pad); + // Skip illegal characters and whitespace + if (c == -1) continue; + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) + result += String.fromCharCode((leftdata >> leftbits) & 0xff); + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) + throw Components.Exception('Corrupted base64 string'); + + return result; +} diff --git a/browser/base/content/test/urlbar/browser.ini b/browser/base/content/test/urlbar/browser.ini new file mode 100644 index 000000000..39bc086c9 --- /dev/null +++ b/browser/base/content/test/urlbar/browser.ini @@ -0,0 +1,101 @@ +[DEFAULT] +support-files = + dummy_page.html + head.js + +[browser_URLBarSetURI.js] +skip-if = (os == "linux" || os == "mac") && debug # bug 970052, bug 970053 +[browser_action_keyword.js] +skip-if = os == "linux" # Bug 1188154 +support-files = + print_postdata.sjs +[browser_action_keyword_override.js] +[browser_action_searchengine.js] +[browser_action_searchengine_alias.js] +[browser_autocomplete_a11y_label.js] +[browser_autocomplete_autoselect.js] +[browser_autocomplete_cursor.js] +[browser_autocomplete_edit_completed.js] +[browser_autocomplete_enter_race.js] +[browser_autocomplete_no_title.js] +[browser_autocomplete_tag_star_visibility.js] +[browser_bug1104165-switchtab-decodeuri.js] +[browser_bug1003461-switchtab-override.js] +[browser_bug1024133-switchtab-override-keynav.js] +[browser_bug1025195_switchToTabHavingURI_aOpenParams.js] +[browser_bug1070778.js] +[browser_bug1225194-remotetab.js] +[browser_bug304198.js] +[browser_bug556061.js] +subsuite = clipboard +[browser_bug562649.js] +[browser_bug623155.js] +support-files = + redirect_bug623155.sjs +[browser_bug783614.js] +[browser_canonizeURL.js] +[browser_dragdropURL.js] +[browser_locationBarCommand.js] +[browser_locationBarExternalLoad.js] +[browser_moz_action_link.js] +[browser_removeUnsafeProtocolsFromURLBarPaste.js] +subsuite = clipboard +[browser_search_favicon.js] +[browser_tabMatchesInAwesomebar.js] +support-files = + moz.png +[browser_tabMatchesInAwesomebar_perwindowpb.js] +skip-if = os == 'linux' # Bug 1104755 +[browser_urlbarAboutHomeLoading.js] +[browser_urlbarAutoFillTrimURLs.js] +[browser_urlbarCopying.js] +subsuite = clipboard +support-files = + authenticate.sjs +[browser_urlbarDecode.js] +[browser_urlbarDelete.js] +[browser_urlbarEnter.js] +[browser_urlbarEnterAfterMouseOver.js] +skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s +[browser_urlbarFocusedCmdK.js] +[browser_urlbarHashChangeProxyState.js] +[browser_urlbarKeepStateAcrossTabSwitches.js] +[browser_urlbarOneOffs.js] +[browser_urlbarPrivateBrowsingWindowChange.js] +[browser_urlbarRaceWithTabs.js] +[browser_urlbarRevert.js] +[browser_urlbarSearchSingleWordNotification.js] +[browser_urlbarSearchSuggestions.js] +support-files = + searchSuggestionEngine.xml + searchSuggestionEngine.sjs +[browser_urlbarSearchSuggestionsNotification.js] +support-files = + searchSuggestionEngine.xml + searchSuggestionEngine.sjs +[browser_urlbarSearchTelemetry.js] +support-files = + searchSuggestionEngine.xml + searchSuggestionEngine.sjs +[browser_urlbarStop.js] +[browser_urlbarTrimURLs.js] +subsuite = clipboard +[browser_urlbarUpdateForDomainCompletion.js] +[browser_urlbar_autoFill_backspaced.js] +[browser_urlbar_blanking.js] +support-files = + file_blank_but_not_blank.html +[browser_urlbar_locationchange_urlbar_edit_dos.js] +support-files = + file_urlbar_edit_dos.html +[browser_urlbar_searchsettings.js] +[browser_urlbar_stop_pending.js] +support-files = + slow-page.sjs +[browser_urlbar_remoteness_switch.js] +run-if = e10s +[browser_urlHighlight.js] +[browser_wyciwyg_urlbarCopying.js] +subsuite = clipboard +support-files = + test_wyciwyg_copying.html diff --git a/browser/base/content/test/urlbar/browser_URLBarSetURI.js b/browser/base/content/test/urlbar/browser_URLBarSetURI.js new file mode 100644 index 000000000..ac8352f1a --- /dev/null +++ b/browser/base/content/test/urlbar/browser_URLBarSetURI.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + // avoid prompting about phishing + Services.prefs.setIntPref(phishyUserPassPref, 32); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(phishyUserPassPref); + }); + + nextTest(); +} + +const phishyUserPassPref = "network.http.phishy-userpass-length"; + +function nextTest() { + let test = tests.shift(); + if (test) { + test(function () { + executeSoon(nextTest); + }); + } else { + executeSoon(finish); + } +} + +var tests = [ + function revert(next) { + loadTabInWindow(window, function (tab) { + gURLBar.handleRevert(); + is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after reverting"); + gBrowser.removeTab(tab); + next(); + }); + }, + function customize(next) { + // Need to wait for delayedStartup for the customization part of the test, + // since that's where BrowserToolboxCustomizeDone is set. + BrowserTestUtils.openNewBrowserWindow().then(function(win) { + loadTabInWindow(win, function () { + openToolbarCustomizationUI(function () { + closeToolbarCustomizationUI(function () { + is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped after customize"); + win.close(); + next(); + }, win); + }, win); + }); + }); + }, + function pageloaderror(next) { + loadTabInWindow(window, function (tab) { + // Load a new URL and then immediately stop it, to simulate a page load + // error. + tab.linkedBrowser.loadURI("http://test1.example.com"); + tab.linkedBrowser.stop(); + is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after load error"); + gBrowser.removeTab(tab); + next(); + }); + } +]; + +function loadTabInWindow(win, callback) { + info("Loading tab"); + let url = "http://user:pass@example.com/"; + let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(url); + BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url).then(() => { + info("Tab loaded"); + is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped initially"); + callback(tab); + }, true); +} + +function openToolbarCustomizationUI(aCallback, aBrowserWin) { + if (!aBrowserWin) + aBrowserWin = window; + + aBrowserWin.gCustomizeMode.enter(); + + aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() { + aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded); + executeSoon(function() { + aCallback(aBrowserWin) + }); + }); +} + +function closeToolbarCustomizationUI(aCallback, aBrowserWin) { + aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() { + aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded); + executeSoon(aCallback); + }); + + aBrowserWin.gCustomizeMode.exit(); +} + diff --git a/browser/base/content/test/urlbar/browser_action_keyword.js b/browser/base/content/test/urlbar/browser_action_keyword.js new file mode 100644 index 000000000..854a7b82f --- /dev/null +++ b/browser/base/content/test/urlbar/browser_action_keyword.js @@ -0,0 +1,119 @@ +function* promise_first_result(inputText) { + yield promiseAutocompleteResultPopup(inputText); + + let firstResult = gURLBar.popup.richlistbox.firstChild; + return firstResult; +} + +const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/print_postdata.sjs"; + +add_task(function* setup() { + yield PlacesUtils.keywords.insert({ keyword: "get", + url: TEST_URL + "?q=%s" }); + yield PlacesUtils.keywords.insert({ keyword: "post", + url: TEST_URL, + postData: "q=%s" }); + registerCleanupFunction(function* () { + yield PlacesUtils.keywords.remove("get"); + yield PlacesUtils.keywords.remove("post"); + while (gBrowser.tabs.length > 1) { + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + } + }); +}); + +add_task(function* get_keyword() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + let result = yield promise_first_result("get something"); + isnot(result, null, "Expect a keyword result"); + + let types = new Set(result.getAttribute("type").split(/\s+/)); + Assert.ok(types.has("keyword")); + is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute"); + is(result.getAttribute("title"), "mochi.test:8888", "Expect correct title"); + + // We need to make a real URI out of this to ensure it's normalised for + // comparison. + let uri = NetUtil.newURI(result.getAttribute("url")); + is(uri.spec, PlacesUtils.mozActionURI("keyword", + { url: TEST_URL + "?q=something", + input: "get something"}), + "Expect correct url"); + + let titleHbox = result._titleText.parentNode.parentNode; + ok(titleHbox.classList.contains("ac-title"), "Title hbox element sanity check"); + is_element_visible(titleHbox, "Title element should be visible"); + is(result._titleText.textContent, "mochi.test:8888: something", + "Node should contain the name of the bookmark and query"); + + let urlHbox = result._urlText.parentNode.parentNode; + ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check"); + is_element_hidden(urlHbox, "URL element should be hidden"); + + let actionHbox = result._actionText.parentNode.parentNode; + ok(actionHbox.classList.contains("ac-action"), "Action hbox element sanity check"); + is_element_visible(actionHbox, "Action element should be visible"); + is(result._actionText.textContent, "", "Action text should be empty"); + + // Click on the result + info("Normal click on result"); + let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + EventUtils.synthesizeMouseAtCenter(result, {}); + yield tabPromise; + is(tab.linkedBrowser.currentURI.spec, TEST_URL + "?q=something", + "Tab should have loaded from clicking on result"); + + // Middle-click on the result + info("Middle-click on result"); + result = yield promise_first_result("get somethingmore"); + isnot(result, null, "Expect a keyword result"); + // We need to make a real URI out of this to ensure it's normalised for + // comparison. + uri = NetUtil.newURI(result.getAttribute("url")); + is(uri.spec, PlacesUtils.mozActionURI("keyword", + { url: TEST_URL + "?q=somethingmore", + input: "get somethingmore" }), + "Expect correct url"); + + tabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + EventUtils.synthesizeMouseAtCenter(result, {button: 1}); + let tabOpenEvent = yield tabPromise; + let newTab = tabOpenEvent.target; + yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser); + is(newTab.linkedBrowser.currentURI.spec, + TEST_URL + "?q=somethingmore", + "Tab should have loaded from middle-clicking on result"); +}); + + +add_task(function* post_keyword() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + let result = yield promise_first_result("post something"); + isnot(result, null, "Expect a keyword result"); + + let types = new Set(result.getAttribute("type").split(/\s+/)); + Assert.ok(types.has("keyword")); + is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute"); + is(result.getAttribute("title"), "mochi.test:8888", "Expect correct title"); + + is(result.getAttribute("url"), + PlacesUtils.mozActionURI("keyword", { url: TEST_URL, + input: "post something", + "postData": "q=something" }), + "Expect correct url"); + + // Click on the result + info("Normal click on result"); + let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + EventUtils.synthesizeMouseAtCenter(result, {}); + yield tabPromise; + is(tab.linkedBrowser.currentURI.spec, TEST_URL, + "Tab should have loaded from clicking on result"); + + let postData = yield ContentTask.spawn(tab.linkedBrowser, null, function* () { + return content.document.body.textContent; + }); + is(postData, "q=something", "post data was submitted correctly"); +}); diff --git a/browser/base/content/test/urlbar/browser_action_keyword_override.js b/browser/base/content/test/urlbar/browser_action_keyword_override.js new file mode 100644 index 000000000..f5a865678 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_action_keyword_override.js @@ -0,0 +1,40 @@ +add_task(function*() { + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }) + + registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.remove(bm); + }); + + yield promiseAutocompleteResultPopup("keyword search"); + let result = gURLBar.popup.richlistbox.children[0]; + + info("Before override"); + let titleHbox = result._titleText.parentNode.parentNode; + ok(titleHbox.classList.contains("ac-title"), "Title hbox element sanity check"); + is_element_visible(titleHbox, "Title element should be visible"); + + let urlHbox = result._urlText.parentNode.parentNode; + ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check"); + is_element_hidden(urlHbox, "URL element should be hidden"); + + let actionHbox = result._actionText.parentNode.parentNode; + ok(actionHbox.classList.contains("ac-action"), "Action hbox element sanity check"); + is_element_visible(actionHbox, "Action element should be visible"); + is(result._actionText.textContent, "", "Action text should be empty"); + + info("During override"); + EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" }); + is_element_visible(titleHbox, "Title element should be visible"); + is_element_hidden(urlHbox, "URL element should be hidden"); + is_element_visible(actionHbox, "Action element should be visible"); + is(result._actionText.textContent, "", "Action text should be empty"); + + EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" }); + + gURLBar.popup.hidePopup(); + yield promisePopupHidden(gURLBar.popup); +}); diff --git a/browser/base/content/test/urlbar/browser_action_searchengine.js b/browser/base/content/test/urlbar/browser_action_searchengine.js new file mode 100644 index 000000000..d2115abba --- /dev/null +++ b/browser/base/content/test/urlbar/browser_action_searchengine.js @@ -0,0 +1,36 @@ +add_task(function* () { + Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", + "http://example.com/?q={searchTerms}"); + let engine = Services.search.getEngineByName("MozSearch"); + let originalEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + registerCleanupFunction(() => { + Services.search.currentEngine = originalEngine; + let engine = Services.search.getEngineByName("MozSearch"); + Services.search.removeEngine(engine); + + try { + gBrowser.removeTab(tab); + } catch (ex) { /* tab may have already been closed in case of failure */ } + + return PlacesTestUtils.clearHistory(); + }); + + yield promiseAutocompleteResultPopup("open a search"); + let result = gURLBar.popup.richlistbox.firstChild; + + isnot(result, null, "Should have a result"); + is(result.getAttribute("url"), + `moz-action:searchengine,{"engineName":"MozSearch","input":"open%20a%20search","searchQuery":"open%20a%20search"}`, + "Result should be a moz-action: for the correct search engine"); + is(result.hasAttribute("image"), false, "Result shouldn't have an image attribute"); + + let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + result.click(); + yield tabPromise; + + is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/?q=open+a+search", "Correct URL should be loaded"); +}); diff --git a/browser/base/content/test/urlbar/browser_action_searchengine_alias.js b/browser/base/content/test/urlbar/browser_action_searchengine_alias.js new file mode 100644 index 000000000..1967d178a --- /dev/null +++ b/browser/base/content/test/urlbar/browser_action_searchengine_alias.js @@ -0,0 +1,35 @@ +add_task(function* () { + let iconURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC"; + Services.search.addEngineWithDetails("MozSearch", iconURI, "moz", "", "GET", + "http://example.com/?q={searchTerms}"); + let engine = Services.search.getEngineByName("MozSearch"); + let originalEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + registerCleanupFunction(() => { + Services.search.currentEngine = originalEngine; + let engine = Services.search.getEngineByName("MozSearch"); + Services.search.removeEngine(engine); + + try { + gBrowser.removeTab(tab); + } catch (ex) { /* tab may have already been closed in case of failure */ } + + return PlacesTestUtils.clearHistory(); + }); + + yield promiseAutocompleteResultPopup("moz open a search"); + + let result = gURLBar.popup.richlistbox.children[0]; + ok(result.hasAttribute("image"), "Result should have an image attribute"); + ok(result.getAttribute("image") === engine.iconURI.spec, + "Image attribute should have the search engine's icon"); + + let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + EventUtils.synthesizeKey("VK_RETURN", { }); + yield tabPromise; + + is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/?q=open+a+search"); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js new file mode 100644 index 000000000..a27f9672e --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const SUGGEST_ALL_PREF = "browser.search.suggest.enabled"; +const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches"; +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; + +add_task(function* switchToTab() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:about"); + + yield promiseAutocompleteResultPopup("% about"); + + ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results"); + let result = gURLBar.popup.richlistbox.children[1]; + is(result.getAttribute("type"), "switchtab", "Expect right type attribute"); + is(result.label, "about:about about:about Tab", "Result a11y label should be: <title> <url> Tab"); + + gURLBar.popup.hidePopup(); + yield promisePopupHidden(gURLBar.popup); + gBrowser.removeTab(tab); +}); + +add_task(function* searchSuggestions() { + let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + let oldCurrentEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true); + registerCleanupFunction(function () { + Services.search.currentEngine = oldCurrentEngine; + Services.prefs.clearUserPref(SUGGEST_ALL_PREF); + Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF); + }); + + yield promiseAutocompleteResultPopup("foo"); + // Don't assume that the search doesn't match history or bookmarks left around + // by earlier tests. + Assert.ok(gURLBar.popup.richlistbox.children.length >= 3, + "Should get at least heuristic result + two search suggestions"); + // The first expected search is the search term itself since the heuristic + // result will come before the search suggestions. + let expectedSearches = [ + "foo", + "foofoo", + "foobar", + ]; + for (let child of gURLBar.popup.richlistbox.children) { + if (child.getAttribute("type").split(/\s+/).indexOf("searchengine") >= 0) { + Assert.ok(expectedSearches.length > 0); + let suggestion = expectedSearches.shift(); + Assert.equal(child.label, suggestion + " browser_searchSuggestionEngine searchSuggestionEngine.xml Search", + "Result label should be: <search term> <engine name> Search"); + } + } + Assert.ok(expectedSearches.length == 0); + gURLBar.closePopup(); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js b/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js new file mode 100644 index 000000000..e4e0daa8e --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js @@ -0,0 +1,92 @@ +const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches"; + +function repeat(limit, func) { + for (let i = 0; i < limit; i++) { + func(i); + } +} + +function is_selected(index) { + is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`); + + // This is true because although both the listbox and the one-offs can have + // selections, the test doesn't check that. + is(gURLBar.popup.oneOffSearchButtons.selectedButton, null, + "A result is selected, so the one-offs should not have a selection"); +} + +function is_selected_one_off(index) { + is(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, index, + "Expected one-off button should be selected"); + + // This is true because although both the listbox and the one-offs can have + // selections, the test doesn't check that. + is(gURLBar.popup.richlistbox.selectedIndex, -1, + "A one-off is selected, so the listbox should not have a selection"); +} + +add_task(function*() { + let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults"); + + Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true); + registerCleanupFunction(function* () { + yield PlacesTestUtils.clearHistory(); + Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF); + }); + + let visits = []; + repeat(maxResults, i => { + visits.push({ + uri: makeURI("http://example.com/autocomplete/?" + i), + }); + }); + yield PlacesTestUtils.addVisits(visits); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + yield promiseAutocompleteResultPopup("example.com/autocomplete"); + + let popup = gURLBar.popup; + let results = popup.richlistbox.children; + is(results.length, maxResults, + "Should get maxResults=" + maxResults + " results"); + is_selected(0); + + info("Key Down to select the next item"); + EventUtils.synthesizeKey("VK_DOWN", {}); + is_selected(1); + + info("Key Down maxResults-1 times should select the first one-off"); + repeat(maxResults - 1, () => EventUtils.synthesizeKey("VK_DOWN", {})); + is_selected_one_off(0); + + info("Key Down numButtons-1 should select the last one-off"); + let numButtons = + gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length; + repeat(numButtons - 1, () => EventUtils.synthesizeKey("VK_DOWN", {})); + is_selected_one_off(numButtons - 1); + + info("Key Down twice more should select the second result"); + repeat(2, () => EventUtils.synthesizeKey("VK_DOWN", {})); + is_selected(1); + + info("Key Down maxResults + numButtons times should wrap around"); + repeat(maxResults + numButtons, + () => EventUtils.synthesizeKey("VK_DOWN", {})); + is_selected(1); + + info("Key Up maxResults + numButtons times should wrap around the other way"); + repeat(maxResults + numButtons, () => EventUtils.synthesizeKey("VK_UP", {})); + is_selected(1); + + info("Page Up will go up the list, but not wrap"); + EventUtils.synthesizeKey("VK_PAGE_UP", {}) + is_selected(0); + + info("Page Up again will wrap around to the end of the list"); + EventUtils.synthesizeKey("VK_PAGE_UP", {}) + is_selected(maxResults - 1); + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield promisePopupHidden(gURLBar.popup); + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_cursor.js b/browser/base/content/test/urlbar/browser_autocomplete_cursor.js new file mode 100644 index 000000000..9cc2c6eac --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_cursor.js @@ -0,0 +1,17 @@ +add_task(function*() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + yield promiseAutocompleteResultPopup("www.mozilla.org"); + + gURLBar.selectTextRange(4, 4); + + is(gURLBar.popup.state, "open", "Popup should be open"); + is(gURLBar.popup.richlistbox.selectedIndex, 0, "Should have selected something"); + + EventUtils.synthesizeKey("VK_RIGHT", {}); + yield promisePopupHidden(gURLBar.popup); + + is(gURLBar.selectionStart, 5, "Should have moved the cursor"); + is(gURLBar.selectionEnd, 5, "And not selected anything"); + + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js b/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js new file mode 100644 index 000000000..19db1a368 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js @@ -0,0 +1,48 @@ +add_task(function*() { + yield PlacesTestUtils.clearHistory(); + + yield PlacesTestUtils.addVisits([ + { uri: makeURI("http://example.com/foo") }, + { uri: makeURI("http://example.com/foo/bar") }, + ]); + + registerCleanupFunction(function* () { + yield PlacesTestUtils.clearHistory(); + }); + + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + gURLBar.focus(); + + yield promiseAutocompleteResultPopup("http://example.com"); + + let popup = gURLBar.popup; + let list = popup.richlistbox; + let initialIndex = list.selectedIndex; + + info("Key Down to select the next item."); + EventUtils.synthesizeKey("VK_DOWN", {}); + + let nextIndex = initialIndex + 1; + let nextValue = gURLBar.controller.getFinalCompleteValueAt(nextIndex); + is(list.selectedIndex, nextIndex, "The next item is selected."); + is(gURLBar.value, nextValue, "The selected URL is completed."); + + info("Press backspace"); + EventUtils.synthesizeKey("VK_BACK_SPACE", {}); + yield promiseSearchComplete(); + + let editedValue = gURLBar.textValue; + is(list.selectedIndex, initialIndex, "The initial index is selected again."); + isnot(editedValue, nextValue, "The URL has changed."); + + let docLoad = waitForDocLoadAndStopIt("http://" + editedValue); + + info("Press return to load edited URL."); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield Promise.all([ + promisePopupHidden(gURLBar.popup), + docLoad, + ]); + + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js b/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js new file mode 100644 index 000000000..4e3c8943c --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js @@ -0,0 +1,122 @@ +// The order of these tests matters! + +add_task(function* setup () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }); + registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.remove(bm); + yield BrowserTestUtils.removeTab(tab); + }); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }); + // Needs at least one success. + ok(true, "Setup complete"); +}); + +add_task(function* test_keyword() { + yield promiseAutocompleteResultPopup("keyword bear"); + gURLBar.focus(); + EventUtils.synthesizeKey("d", {}); + EventUtils.synthesizeKey("VK_RETURN", {}); + info("wait for the page to load"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser, + false, "http://example.com/?q=beard"); +}); + +add_task(function* test_sametext() { + yield promiseAutocompleteResultPopup("example.com", window, true); + + // Simulate re-entering the same text searched the last time. This may happen + // through a copy paste, but clipboard handling is not much reliable, so just + // fire an input event. + info("synthesize input event"); + let event = document.createEvent("Events"); + event.initEvent("input", true, true); + gURLBar.dispatchEvent(event); + EventUtils.synthesizeKey("VK_RETURN", {}); + + info("wait for the page to load"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser, + false, "http://example.com/"); +}); + +add_task(function* test_after_empty_search() { + yield promiseAutocompleteResultPopup(""); + gURLBar.focus(); + gURLBar.value = "e"; + EventUtils.synthesizeKey("x", {}); + EventUtils.synthesizeKey("VK_RETURN", {}); + + info("wait for the page to load"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser, + false, "http://example.com/"); +}); + +add_task(function* test_disabled_ac() { + // Disable autocomplete. + let suggestHistory = Preferences.get("browser.urlbar.suggest.history"); + Preferences.set("browser.urlbar.suggest.history", false); + let suggestBookmarks = Preferences.get("browser.urlbar.suggest.bookmark"); + Preferences.set("browser.urlbar.suggest.bookmark", false); + let suggestOpenPages = Preferences.get("browser.urlbar.suggest.openpage"); + Preferences.set("browser.urlbar.suggest.openpages", false); + + Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", + "http://example.com/?q={searchTerms}"); + let engine = Services.search.getEngineByName("MozSearch"); + let originalEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + + function* cleanup() { + Preferences.set("browser.urlbar.suggest.history", suggestHistory); + Preferences.set("browser.urlbar.suggest.bookmark", suggestBookmarks); + Preferences.set("browser.urlbar.suggest.openpage", suggestOpenPages); + + Services.search.currentEngine = originalEngine; + let engine = Services.search.getEngineByName("MozSearch"); + if (engine) { + Services.search.removeEngine(engine); + } + } + registerCleanupFunction(cleanup); + + gURLBar.focus(); + gURLBar.value = "e"; + EventUtils.synthesizeKey("x", {}); + EventUtils.synthesizeKey("VK_RETURN", {}); + + info("wait for the page to load"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser, + false, "http://example.com/?q=ex"); + yield cleanup(); +}); + +add_task(function* test_delay() { + const TIMEOUT = 10000; + // Set a large delay. + let delay = Preferences.get("browser.urlbar.delay"); + Preferences.set("browser.urlbar.delay", TIMEOUT); + + registerCleanupFunction(function* () { + Preferences.set("browser.urlbar.delay", delay); + }); + + // This is needed to clear the current value, otherwise autocomplete may think + // the user removed text from the end. + let start = Date.now(); + yield promiseAutocompleteResultPopup(""); + Assert.ok((Date.now() - start) < TIMEOUT); + + start = Date.now(); + gURLBar.closePopup(); + gURLBar.focus(); + gURLBar.value = "e"; + EventUtils.synthesizeKey("x", {}); + EventUtils.synthesizeKey("VK_RETURN", {}); + info("wait for the page to load"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser, + false, "http://example.com/"); + Assert.ok((Date.now() - start) < TIMEOUT); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_no_title.js b/browser/base/content/test/urlbar/browser_autocomplete_no_title.js new file mode 100644 index 000000000..8d608550b --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_no_title.js @@ -0,0 +1,15 @@ +add_task(function*() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + let uri = NetUtil.newURI("http://bug1060642.example.com/beards/are/pretty/great"); + yield PlacesTestUtils.addVisits([{uri: uri, title: ""}]); + + yield promiseAutocompleteResultPopup("bug1060642"); + ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results"); + let result = gURLBar.popup.richlistbox.children[1]; + is(result._titleText.textContent, "bug1060642.example.com", "Result title should be as expected"); + + gURLBar.popup.hidePopup(); + yield promisePopupHidden(gURLBar.popup); + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js b/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js new file mode 100644 index 000000000..8a69b4b44 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js @@ -0,0 +1,102 @@ +add_task(function*() { + registerCleanupFunction(() => { + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + }); + + function* addTagItem(tagName) { + let uri = NetUtil.newURI(`http://example.com/this/is/tagged/${tagName}`); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + uri, + PlacesUtils.bookmarks.DEFAULT_INDEX, + `test ${tagName}`); + PlacesUtils.tagging.tagURI(uri, [tagName]); + yield PlacesTestUtils.addVisits([{uri: uri, title: `Test page with tag ${tagName}`}]); + } + + // We use different tags for each part of the test, as otherwise the + // autocomplete code tries to be smart by using the previously cached element + // without updating it (since all parameters it knows about are the same). + + let testcases = [{ + description: "Test with suggest.bookmark=true", + tagName: "tagtest1", + prefs: { + "suggest.bookmark": true, + }, + input: "tagtest1", + expected: { + type: "bookmark", + typeImageVisible: true, + }, + }, { + description: "Test with suggest.bookmark=false", + tagName: "tagtest2", + prefs: { + "suggest.bookmark": false, + }, + input: "tagtest2", + expected: { + type: "tag", + typeImageVisible: false, + }, + }, { + description: "Test with suggest.bookmark=true (again)", + tagName: "tagtest3", + prefs: { + "suggest.bookmark": true, + }, + input: "tagtest3", + expected: { + type: "bookmark", + typeImageVisible: true, + }, + }, { + description: "Test with bookmark restriction token", + tagName: "tagtest4", + prefs: { + "suggest.bookmark": true, + }, + input: "* tagtest4", + expected: { + type: "bookmark", + typeImageVisible: true, + }, + }, { + description: "Test with history restriction token", + tagName: "tagtest5", + prefs: { + "suggest.bookmark": true, + }, + input: "^ tagtest5", + expected: { + type: "tag", + typeImageVisible: false, + }, + }]; + + for (let testcase of testcases) { + info(`Test case: ${testcase.description}`); + + yield addTagItem(testcase.tagName); + for (let prefName of Object.keys(testcase.prefs)) { + Services.prefs.setBoolPref(`browser.urlbar.${prefName}`, testcase.prefs[prefName]); + } + + yield promiseAutocompleteResultPopup(testcase.input); + let result = gURLBar.popup.richlistbox.children[1]; + ok(result && !result.collasped, "Should have result"); + + is(result.getAttribute("type"), testcase.expected.type, "Result should have expected type"); + + let typeIconStyle = window.getComputedStyle(result._typeIcon); + let imageURL = typeIconStyle.listStyleImage; + if (testcase.expected.typeImageVisible) { + ok(/^url\(.+\)$/.test(imageURL), "Type image should be visible"); + } else { + is(imageURL, "none", "Type image should be hidden"); + } + + gURLBar.popup.hidePopup(); + yield promisePopupHidden(gURLBar.popup); + } +}); diff --git a/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js new file mode 100644 index 000000000..89f604491 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* test_switchtab_override() { + let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + + info("Opening first tab"); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL); + + info("Opening and selecting second tab"); + let secondTab = gBrowser.selectedTab = gBrowser.addTab(); + registerCleanupFunction(() => { + try { + gBrowser.removeTab(tab); + gBrowser.removeTab(secondTab); + } catch (ex) { /* tabs may have already been closed in case of failure */ } + }); + + info("Wait for autocomplete") + let deferred = Promise.defer(); + let onSearchComplete = gURLBar.onSearchComplete; + registerCleanupFunction(() => { + gURLBar.onSearchComplete = onSearchComplete; + }); + gURLBar.onSearchComplete = function () { + ok(gURLBar.popupOpen, "The autocomplete popup is correctly open"); + onSearchComplete.apply(gURLBar); + deferred.resolve(); + } + + gURLBar.focus(); + gURLBar.value = "dummy_pag"; + EventUtils.synthesizeKey("e", {}); + yield deferred.promise; + + info("Select second autocomplete popup entry"); + EventUtils.synthesizeKey("VK_DOWN", {}); + ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found"); + + info("Override switch-to-tab"); + deferred = Promise.defer(); + // In case of failure this would switch tab. + let onTabSelect = event => { + deferred.reject(new Error("Should have overridden switch to tab")); + }; + gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false); + registerCleanupFunction(() => { + gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false); + }); + // Otherwise it would load the page. + BrowserTestUtils.browserLoaded(secondTab.linkedBrowser).then(deferred.resolve); + + EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" }); + EventUtils.synthesizeKey("VK_RETURN", { }); + info(`gURLBar.value = ${gURLBar.value}`); + EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" }); + yield deferred.promise; + + yield PlacesTestUtils.clearHistory(); +}); diff --git a/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js new file mode 100644 index 000000000..2d97ea07b --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* test_switchtab_override_keynav() { + let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + + info("Opening first tab"); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL); + + info("Opening and selecting second tab"); + let secondTab = gBrowser.selectedTab = gBrowser.addTab(); + registerCleanupFunction(() => { + try { + gBrowser.removeTab(tab); + gBrowser.removeTab(secondTab); + } catch (ex) { /* tabs may have already been closed in case of failure */ } + return PlacesTestUtils.clearHistory(); + }); + + gURLBar.focus(); + gURLBar.value = "dummy_pag"; + EventUtils.synthesizeKey("e", {}); + yield promiseSearchComplete(); + + info("Select second autocomplete popup entry"); + EventUtils.synthesizeKey("VK_DOWN", {}); + ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found"); + + info("Shift+left on switch-to-tab entry"); + + EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" }); + EventUtils.synthesizeKey("VK_LEFT", { shiftKey: true }); + EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" }); + + ok(!/moz-action:switchtab/.test(gURLBar.inputField.value), "switch to tab should be hidden"); +}); diff --git a/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js b/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js new file mode 100644 index 000000000..9e779ade1 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* test_ignoreFragment() { + let tabRefAboutHome = + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home#1"); + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + let numTabsAtStart = gBrowser.tabs.length; + + switchTab("about:home#1", true); + switchTab("about:mozilla", true); + + let hashChangePromise = ContentTask.spawn(tabRefAboutHome.linkedBrowser, null, function* () { + yield ContentTaskUtils.waitForEvent(this, "hashchange", false); + }); + switchTab("about:home#2", true, { ignoreFragment: "whenComparingAndReplace" }); + is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to"); + yield hashChangePromise; + is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref"); + switchTab("about:mozilla", true); + switchTab("about:home#3", true, { ignoreFragment: "whenComparing" }); + is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to"); + is(gBrowser.currentURI.ref, "2", "The ref should be unchanged since the fragment is only ignored when comparing"); + switchTab("about:mozilla", true); + switchTab("about:home#1", false); + isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab"); + is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened"); + switchTab("about:mozilla", true); + switchTab("about:home", true, {ignoreFragment: "whenComparingAndReplace"}); + yield BrowserTestUtils.waitForCondition(function() { + return tabRefAboutHome.linkedBrowser.currentURI.spec == "about:home"; + }); + is(tabRefAboutHome.linkedBrowser.currentURI.spec, "about:home", "about:home shouldn't have hash"); + switchTab("about:about", false, { ignoreFragment: "whenComparingAndReplace" }); + cleanupTestTabs(); +}); + +add_task(function* test_ignoreQueryString() { + let tabRefAboutHome = + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox"); + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + switchTab("about:home?hello=firefox", true); + switchTab("about:home?hello=firefoxos", false); + // Remove the last opened tab to test ignoreQueryString option. + gBrowser.removeCurrentTab(); + switchTab("about:home?hello=firefoxos", true, { ignoreQueryString: true }); + is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab"); + is(gBrowser.currentURI.spec, "about:home?hello=firefox", "The spec should NOT be updated to the new query string"); + cleanupTestTabs(); +}); + +add_task(function* test_replaceQueryString() { + let tabRefAboutHome = + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox"); + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + switchTab("about:home", false); + switchTab("about:home?hello=firefox", true); + switchTab("about:home?hello=firefoxos", false); + // Remove the last opened tab to test replaceQueryString option. + gBrowser.removeCurrentTab(); + switchTab("about:home?hello=firefoxos", true, { replaceQueryString: true }); + is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab"); + // Wait for the tab to load the new URI spec. + yield BrowserTestUtils.browserLoaded(tabRefAboutHome.linkedBrowser); + is(gBrowser.currentURI.spec, "about:home?hello=firefoxos", "The spec should be updated to the new spec"); + cleanupTestTabs(); +}); + +add_task(function* test_replaceQueryStringAndFragment() { + let tabRefAboutHome = + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox#aaa"); + let tabRefAboutMozilla = + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla?hello=firefoxos#aaa"); + + switchTab("about:home", false); + gBrowser.removeCurrentTab(); + switchTab("about:home?hello=firefox#aaa", true); + is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab"); + switchTab("about:mozilla?hello=firefox#bbb", true, { replaceQueryString: true, ignoreFragment: "whenComparingAndReplace" }); + is(tabRefAboutMozilla, gBrowser.selectedTab, "Selected tab should be the initial about:mozilla tab"); + switchTab("about:home?hello=firefoxos#bbb", true, { ignoreQueryString: true, ignoreFragment: "whenComparingAndReplace" }); + is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab"); + cleanupTestTabs(); +}); + +add_task(function* test_ignoreQueryStringIgnoresFragment() { + let tabRefAboutHome = + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox#aaa"); + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla?hello=firefoxos#aaa"); + + switchTab("about:home?hello=firefox#bbb", false, { ignoreQueryString: true }); + gBrowser.removeCurrentTab(); + switchTab("about:home?hello=firefoxos#aaa", true, { ignoreQueryString: true }); + is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab"); + cleanupTestTabs(); +}); + +// Begin helpers + +function cleanupTestTabs() { + while (gBrowser.tabs.length > 1) + gBrowser.removeCurrentTab(); +} + +function switchTab(aURI, aShouldFindExistingTab, aOpenParams = {}) { + // Build the description before switchToTabHavingURI deletes the object properties. + let msg = `Should switch to existing ${aURI} tab if one existed, ` + + `${(aOpenParams.ignoreFragment ? "ignoring" : "including")} fragment portion, `; + if (aOpenParams.replaceQueryString) { + msg += "replacing"; + } else if (aOpenParams.ignoreQueryString) { + msg += "ignoring"; + } else { + msg += "including"; + } + msg += " query string."; + let tabFound = switchToTabHavingURI(aURI, true, aOpenParams); + is(tabFound, aShouldFindExistingTab, msg); +} + +registerCleanupFunction(cleanupTestTabs); diff --git a/browser/base/content/test/urlbar/browser_bug1070778.js b/browser/base/content/test/urlbar/browser_bug1070778.js new file mode 100644 index 000000000..ab88d04d8 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug1070778.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function is_selected(index) { + is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`); +} + +add_task(function*() { + let bookmarks = []; + bookmarks.push((yield PlacesUtils.bookmarks + .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/?q=%s", + title: "test" }))); + yield PlacesUtils.keywords.insert({ keyword: "keyword", + url: "http://example.com/?q=%s" }); + + // This item only needed so we can select the keyword item, select something + // else, then select the keyword item again. + bookmarks.push((yield PlacesUtils.bookmarks + .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/keyword", + title: "keyword abc" }))); + + registerCleanupFunction(function* () { + for (let bm of bookmarks) { + yield PlacesUtils.bookmarks.remove(bm); + } + }); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + yield promiseAutocompleteResultPopup("keyword a"); + + // First item should already be selected + is_selected(0); + // Select next one (important!) + EventUtils.synthesizeKey("VK_DOWN", {}); + is_selected(1); + // Re-select keyword item + EventUtils.synthesizeKey("VK_UP", {}); + is_selected(0); + + EventUtils.synthesizeKey("b", {}); + yield promiseSearchComplete(); + + is(gURLBar.textValue, "keyword ab", "urlbar should have expected input"); + + let result = gURLBar.popup.richlistbox.firstChild; + isnot(result, null, "Should have first item"); + let uri = NetUtil.newURI(result.getAttribute("url")); + is(uri.spec, PlacesUtils.mozActionURI("keyword", {url: "http://example.com/?q=ab", input: "keyword ab"}), "Expect correct url"); + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield promisePopupHidden(gURLBar.popup); + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js new file mode 100644 index 000000000..d165d7304 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js @@ -0,0 +1,29 @@ +add_task(function* test_switchtab_decodeuri() { + info("Opening first tab"); + const TEST_URL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#test%7C1"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + info("Opening and selecting second tab"); + gBrowser.selectedTab = gBrowser.addTab(); + + info("Wait for autocomplete") + yield promiseAutocompleteResultPopup("dummy_page"); + + info("Select autocomplete popup entry"); + EventUtils.synthesizeKey("VK_DOWN", {}); + ok(gURLBar.value.startsWith("moz-action:switchtab"), "switch to tab entry found"); + + info("switch-to-tab"); + yield new Promise((resolve, reject) => { + // In case of success it should switch tab. + gBrowser.tabContainer.addEventListener("TabSelect", function select() { + gBrowser.tabContainer.removeEventListener("TabSelect", select, false); + is(gBrowser.selectedTab, tab, "Should have switched to the right tab"); + resolve(); + }, false); + EventUtils.synthesizeKey("VK_RETURN", { }); + }); + + gBrowser.removeCurrentTab(); + yield PlacesTestUtils.clearHistory(); +}); diff --git a/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js new file mode 100644 index 000000000..3b4a44e76 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js @@ -0,0 +1,16 @@ +add_task(function* test_remotetab_opens() { + const url = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + yield BrowserTestUtils.withNewTab({url: "about:robots", gBrowser}, function* () { + // Set the urlbar to include the moz-action + gURLBar.value = "moz-action:remotetab," + JSON.stringify({ url }); + // Focus the urlbar so we can press enter + gURLBar.focus(); + + // The URL is going to open in the current tab as it is currently about:blank + let promiseTabLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield promiseTabLoaded; + + Assert.equal(gBrowser.selectedTab.linkedBrowser.currentURI.spec, url, "correct URL loaded"); + }); +}); diff --git a/browser/base/content/test/urlbar/browser_bug304198.js b/browser/base/content/test/urlbar/browser_bug304198.js new file mode 100644 index 000000000..dc8d39fae --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug304198.js @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* () { + let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL; + + charsToDelete = 5; + deletedURLTab = gBrowser.addTab(); + fullURLTab = gBrowser.addTab(); + partialURLTab = gBrowser.addTab(); + testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + + let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL); + let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL); + let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL); + deletedURLTab.linkedBrowser.loadURI(testURL); + fullURLTab.linkedBrowser.loadURI(testURL); + partialURLTab.linkedBrowser.loadURI(testURL); + yield Promise.all([loaded1, loaded2, loaded3]); + + testURL = gURLBar.trimValue(testURL); + testPartialURL = testURL.substr(0, (testURL.length - charsToDelete)); + + function cleanUp() { + gBrowser.removeTab(fullURLTab); + gBrowser.removeTab(partialURLTab); + gBrowser.removeTab(deletedURLTab); + } + + function* cycleTabs() { + yield BrowserTestUtils.switchTab(gBrowser, fullURLTab); + is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab'); + + yield BrowserTestUtils.switchTab(gBrowser, partialURLTab); + is(gURLBar.textValue, testPartialURL, 'gURLBar.textValue should be testPartialURL after switching back to partialURLTab'); + yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab); + is(gURLBar.textValue, '', 'gURLBar.textValue should be "" after switching back to deletedURLTab'); + + yield BrowserTestUtils.switchTab(gBrowser, fullURLTab); + is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab'); + } + + function urlbarBackspace() { + return new Promise((resolve, reject) => { + gBrowser.selectedBrowser.focus(); + gURLBar.addEventListener("input", function () { + gURLBar.removeEventListener("input", arguments.callee, false); + resolve(); + }, false); + gURLBar.focus(); + if (gURLBar.selectionStart == gURLBar.selectionEnd) { + gURLBar.selectionStart = gURLBar.selectionEnd = gURLBar.textValue.length; + } + EventUtils.synthesizeKey("VK_BACK_SPACE", {}); + }); + } + + function* prepareDeletedURLTab() { + yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab); + is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to deletedURLTab'); + + // simulate the user removing the whole url from the location bar + gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", true); + + yield urlbarBackspace(); + is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)'); + if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) { + gPrefService.clearUserPref("browser.urlbar.clickSelectsAll"); + } + } + + function* prepareFullURLTab() { + yield BrowserTestUtils.switchTab(gBrowser, fullURLTab); + is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to fullURLTab'); + } + + function* preparePartialURLTab() { + yield BrowserTestUtils.switchTab(gBrowser, partialURLTab); + is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to partialURLTab'); + + // simulate the user removing part of the url from the location bar + gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", false); + + let deleted = 0; + while (deleted < charsToDelete) { + yield urlbarBackspace(arguments.callee); + deleted++; + } + + is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)"); + if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) { + gPrefService.clearUserPref("browser.urlbar.clickSelectsAll"); + } + } + + // prepare the three tabs required by this test + + // First tab + yield* prepareFullURLTab(); + yield* preparePartialURLTab(); + yield* prepareDeletedURLTab(); + + // now cycle the tabs and make sure everything looks good + yield* cycleTabs(); + cleanUp(); +}); + + diff --git a/browser/base/content/test/urlbar/browser_bug556061.js b/browser/base/content/test/urlbar/browser_bug556061.js new file mode 100644 index 000000000..4c6ac5bf5 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug556061.js @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; +var testActionURL = "moz-action:switchtab," + JSON.stringify({url: testURL}); +testURL = gURLBar.trimValue(testURL); +var testTab; + +function runNextTest() { + if (tests.length) { + let t = tests.shift(); + waitForClipboard(t.expected, t.setup, function() { + t.success(); + runNextTest(); + }, cleanup); + } + else { + cleanup(); + } +} + +function cleanup() { + gBrowser.removeTab(testTab); + finish(); +} + +var tests = [ + { + expected: testURL, + setup: function() { + gURLBar.value = testActionURL; + gURLBar.valueIsTyped = true; + is(gURLBar.value, testActionURL, "gURLBar starts with the correct real value"); + is(gURLBar.textValue, testURL, "gURLBar starts with the correct display value"); + + // Focus the urlbar so we can select it all & copy + gURLBar.focus(); + gURLBar.select(); + goDoCommand("cmd_copy"); + }, + success: function() { + is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying"); + } + }, + { + expected: testURL.substring(0, 10), + setup: function() { + // Set selectionStart/End manually and make sure it matches the substring + gURLBar.selectionStart = 0; + gURLBar.selectionEnd = 10; + goDoCommand("cmd_copy"); + }, + success: function() { + is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying"); + } + }, + { + expected: testURL, + setup: function() { + // Setup for cut test... + // Select all + gURLBar.select(); + goDoCommand("cmd_cut"); + }, + success: function() { + is(gURLBar.value, "", "gURLBar.value is now empty"); + } + }, + { + expected: testURL.substring(testURL.length - 10, testURL.length), + setup: function() { + // Reset urlbar value + gURLBar.value = testActionURL; + gURLBar.valueIsTyped = true; + // Sanity check that we have the right value + is(gURLBar.value, testActionURL, "gURLBar starts with the correct real value"); + is(gURLBar.textValue, testURL, "gURLBar starts with the correct display value"); + + // Now just select part of the value & cut that. + gURLBar.selectionStart = testURL.length - 10; + gURLBar.selectionEnd = testURL.length; + goDoCommand("cmd_cut"); + }, + success: function() { + is(gURLBar.value, testURL.substring(0, testURL.length - 10), "gURLBar.value has the correct value"); + } + } +]; + +function test() { + waitForExplicitFinish(); + testTab = gBrowser.addTab(); + gBrowser.selectedTab = testTab; + + // Kick off the testing + runNextTest(); +} diff --git a/browser/base/content/test/urlbar/browser_bug562649.js b/browser/base/content/test/urlbar/browser_bug562649.js new file mode 100644 index 000000000..f56e430ee --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug562649.js @@ -0,0 +1,24 @@ +function test() { + const URI = "data:text/plain,bug562649"; + browserDOMWindow.openURI(makeURI(URI), + null, + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); + + is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI"); + is(gURLBar.value, URI, "location bar value matches test URI"); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.removeCurrentTab({ skipPermitUnload: true }); + is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs"); + is(gURLBar.value, URI, "location bar value matches test URI after switching tabs"); + + waitForExplicitFinish(); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => { + is(gBrowser.userTypedValue, null, "userTypedValue is null as the page has loaded"); + is(gURLBar.value, URI, "location bar value matches test URI as the page has loaded"); + + gBrowser.removeCurrentTab({ skipPermitUnload: true }); + finish(); + }); +} diff --git a/browser/base/content/test/urlbar/browser_bug623155.js b/browser/base/content/test/urlbar/browser_bug623155.js new file mode 100644 index 000000000..dd6ff8c85 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug623155.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const REDIRECT_FROM = "https://example.com/browser/browser/base/content/test/urlbar/" + + "redirect_bug623155.sjs"; + +const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host. + +function isRedirectedURISpec(aURISpec) { + return isRedirectedURI(Services.io.newURI(aURISpec, null, null)); +} + +function isRedirectedURI(aURI) { + // Compare only their before-hash portion. + return Services.io.newURI(REDIRECT_TO, null, null) + .equalsExceptRef(aURI); +} + +/* + Test. + +1. Load +https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#BG + in a background tab. + +2. The redirected URI is <https://www.bank1.com/#BG>, which displayes a cert + error page. + +3. Switch the tab to foreground. + +4. Check the URLbar's value, expecting <https://www.bank1.com/#BG> + +5. Load +https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#FG + in the foreground tab. + +6. The redirected URI is <https://www.bank1.com/#FG>. And this is also + a cert-error page. + +7. Check the URLbar's value, expecting <https://www.bank1.com/#FG> + +8. End. + + */ + +var gNewTab; + +function test() { + waitForExplicitFinish(); + + // Load a URI in the background. + gNewTab = gBrowser.addTab(REDIRECT_FROM + "#BG"); + gBrowser.getBrowserForTab(gNewTab) + .webProgress + .addProgressListener(gWebProgressListener, + Components.interfaces.nsIWebProgress + .NOTIFY_LOCATION); +} + +var gWebProgressListener = { + QueryInterface: function(aIID) { + if (aIID.equals(Components.interfaces.nsIWebProgressListener) || + aIID.equals(Components.interfaces.nsISupportsWeakReference) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + }, + + // --------------------------------------------------------------------------- + // NOTIFY_LOCATION mode should work fine without these methods. + // + // onStateChange: function() {}, + // onStatusChange: function() {}, + // onProgressChange: function() {}, + // onSecurityChange: function() {}, + // ---------------------------------------------------------------------------- + + onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) { + if (!aRequest) { + // This is bug 673752, or maybe initial "about:blank". + return; + } + + ok(gNewTab, "There is a new tab."); + ok(isRedirectedURI(aLocation), + "onLocationChange catches only redirected URI."); + + if (aLocation.ref == "BG") { + // This is background tab's request. + isnot(gNewTab, gBrowser.selectedTab, "This is a background tab."); + } else if (aLocation.ref == "FG") { + // This is foreground tab's request. + is(gNewTab, gBrowser.selectedTab, "This is a foreground tab."); + } + else { + // We shonuld not reach here. + ok(false, "This URI hash is not expected:" + aLocation.ref); + } + + let isSelectedTab = gNewTab.selected; + setTimeout(delayed, 0, isSelectedTab); + } +}; + +function delayed(aIsSelectedTab) { + // Switch tab and confirm URL bar. + if (!aIsSelectedTab) { + gBrowser.selectedTab = gNewTab; + } + + let currentURI = gBrowser.selectedBrowser.currentURI.spec; + ok(isRedirectedURISpec(currentURI), + "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab); + is(gURLBar.value, currentURI, + "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab); + + if (!aIsSelectedTab) { + // If this was a background request, go on a foreground request. + gBrowser.selectedBrowser.loadURI(REDIRECT_FROM + "#FG"); + } + else { + // Othrewise, nothing to do remains. + finish(); + } +} + +/* Cleanup */ +registerCleanupFunction(function() { + if (gNewTab) { + gBrowser.getBrowserForTab(gNewTab) + .webProgress + .removeProgressListener(gWebProgressListener); + + gBrowser.removeTab(gNewTab); + } + gNewTab = null; +}); diff --git a/browser/base/content/test/urlbar/browser_bug783614.js b/browser/base/content/test/urlbar/browser_bug783614.js new file mode 100644 index 000000000..ebc62e8fa --- /dev/null +++ b/browser/base/content/test/urlbar/browser_bug783614.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + gURLBar.focus(); + gURLBar.inputField.value = "https://example.com/"; + gURLBar.selectionStart = 4; + gURLBar.selectionEnd = 5; + goDoCommand("cmd_cut"); + is(gURLBar.inputField.value, "http://example.com/", "location bar value after cutting 's' from https"); + gURLBar.handleRevert(); +} diff --git a/browser/base/content/test/urlbar/browser_canonizeURL.js b/browser/base/content/test/urlbar/browser_canonizeURL.js new file mode 100644 index 000000000..59ab54ca0 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_canonizeURL.js @@ -0,0 +1,42 @@ +add_task(function*() { + let testcases = [ + ["example", "http://www.example.net/", { shiftKey: true }], + // Check that a direct load is not overwritten by a previous canonization. + ["http://example.com/test/", "http://example.com/test/", {}], + ["ex-ample", "http://www.ex-ample.net/", { shiftKey: true }], + [" example ", "http://www.example.net/", { shiftKey: true }], + [" example/foo ", "http://www.example.net/foo", { shiftKey: true }], + [" example/foo bar ", "http://www.example.net/foo%20bar", { shiftKey: true }], + ["example.net", "http://example.net/", { shiftKey: true }], + ["http://example", "http://example/", { shiftKey: true }], + ["example:8080", "http://example:8080/", { shiftKey: true }], + ["ex-ample.foo", "http://ex-ample.foo/", { shiftKey: true }], + ["example.foo/bar ", "http://example.foo/bar", { shiftKey: true }], + ["1.1.1.1", "http://1.1.1.1/", { shiftKey: true }], + ["ftp://example", "ftp://example/", { shiftKey: true }], + ["ftp.example.bar", "http://ftp.example.bar/", { shiftKey: true }], + ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec, { shiftKey: true }], + ]; + + // Disable autoFill for this test, since it could mess up the results. + let autoFill = Preferences.get("browser.urlbar.autoFill"); + Preferences.set("browser.urlbar.autoFill", false); + registerCleanupFunction(() => { + Preferences.set("browser.urlbar.autoFill", autoFill); + }); + + for (let [inputValue, expectedURL, options] of testcases) { + let promiseLoad = waitForDocLoadAndStopIt(expectedURL); + gURLBar.focus(); + if (Object.keys(options).length > 0) { + gURLBar.selectionStart = gURLBar.selectionEnd = + gURLBar.inputField.value.length; + gURLBar.inputField.value = inputValue.slice(0, -1); + EventUtils.synthesizeKey(inputValue.slice(-1), {}); + } else { + gURLBar.textValue = inputValue; + } + EventUtils.synthesizeKey("VK_RETURN", options); + yield promiseLoad; + } +}); diff --git a/browser/base/content/test/urlbar/browser_dragdropURL.js b/browser/base/content/test/urlbar/browser_dragdropURL.js new file mode 100644 index 000000000..ec2906700 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_dragdropURL.js @@ -0,0 +1,15 @@ +"use strict"; + +const TEST_URL = "data:text/html,a test page"; +const DRAG_URL = "http://www.example.com/"; + +add_task(function* checkURLBarUpdateForDrag() { + yield BrowserTestUtils.withNewTab(TEST_URL, function* (browser) { + // Have to use something other than the URL bar as a source, so picking the + // downloads button somewhat arbitrarily: + EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar, + [[{type: "text/plain", data: DRAG_URL}]], "copy", window); + is(gURLBar.value, TEST_URL, "URL bar value should not have changed"); + is(gBrowser.selectedBrowser.userTypedValue, null, "Stored URL bar value should not have changed"); + }); +}); diff --git a/browser/base/content/test/urlbar/browser_locationBarCommand.js b/browser/base/content/test/urlbar/browser_locationBarCommand.js new file mode 100644 index 000000000..935bdf758 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_locationBarCommand.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_VALUE = "example.com"; +const START_VALUE = "example.org"; + +add_task(function* setup() { + Services.prefs.setBoolPref("browser.altClickSave", true); + + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.altClickSave"); + }); +}); + +add_task(function* alt_left_click_test() { + info("Running test: Alt left click"); + + // Monkey patch saveURL() to avoid dealing with file save code paths. + let oldSaveURL = saveURL; + let saveURLPromise = new Promise(resolve => { + saveURL = () => { + // Restore old saveURL() value. + saveURL = oldSaveURL; + resolve(); + }; + }); + + triggerCommand(true, {altKey: true}); + + yield saveURLPromise; + ok(true, "SaveURL was called"); + is(gURLBar.value, "", "Urlbar reverted to original value"); +}); + +add_task(function* shift_left_click_test() { + info("Running test: Shift left click"); + + let newWindowPromise = BrowserTestUtils.waitForNewWindow(); + triggerCommand(true, {shiftKey: true}); + let win = yield newWindowPromise; + + // Wait for the initial browser to load. + let browser = win.gBrowser.selectedBrowser; + let destinationURL = "http://" + TEST_VALUE + "/"; + yield Promise.all([ + BrowserTestUtils.browserLoaded(browser), + BrowserTestUtils.waitForLocationChange(win.gBrowser, destinationURL) + ]); + + info("URL should be loaded in a new window"); + is(gURLBar.value, "", "Urlbar reverted to original value"); + yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser); + is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused"); + is(win.gURLBar.textValue, TEST_VALUE, "New URL is loaded in new window"); + + // Cleanup. + yield BrowserTestUtils.closeWindow(win); +}); + +add_task(function* right_click_test() { + info("Running test: Right click on go button"); + + // Add a new tab. + yield* promiseOpenNewTab(); + + triggerCommand(true, {button: 2}); + + // Right click should do nothing (context menu will be shown). + is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered"); + + // Cleanup. + gBrowser.removeCurrentTab(); +}); + +add_task(function* shift_accel_left_click_test() { + info("Running test: Shift+Ctrl/Cmd left click on go button"); + + // Add a new tab. + let tab = yield* promiseOpenNewTab(); + + let loadStartedPromise = promiseLoadStarted(); + triggerCommand(true, {accelKey: true, shiftKey: true}); + yield loadStartedPromise; + + // Check the load occurred in a new background tab. + info("URL should be loaded in a new background tab"); + is(gURLBar.value, "", "Urlbar reverted to original value"); + ok(!gURLBar.focused, "Urlbar is no longer focused after urlbar command"); + is(gBrowser.selectedTab, tab, "Focus did not change to the new tab"); + + // Select the new background tab + gBrowser.selectedTab = gBrowser.selectedTab.nextSibling; + is(gURLBar.value, TEST_VALUE, "New URL is loaded in new tab"); + + // Cleanup. + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); +}); + +add_task(function* load_in_current_tab_test() { + let tests = [ + {desc: "Simple return keypress"}, + {desc: "Left click on go button", click: true}, + {desc: "Ctrl/Cmd+Return keypress", event: {accelKey: true}}, + {desc: "Alt+Return keypress in a blank tab", event: {altKey: true}} + ]; + + for (let test of tests) { + info(`Running test: ${test.desc}`); + + // Add a new tab. + let tab = yield* promiseOpenNewTab(); + + // Trigger a load and check it occurs in the current tab. + let loadStartedPromise = promiseLoadStarted(); + triggerCommand(test.click || false, test.event || {}); + yield loadStartedPromise; + + info("URL should be loaded in the current tab"); + is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered"); + yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser); + is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused"); + is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab"); + + // Cleanup. + gBrowser.removeCurrentTab(); + } +}); + +add_task(function* load_in_new_tab_test() { + let tests = [ + {desc: "Ctrl/Cmd left click on go button", click: true, event: {accelKey: true}}, + {desc: "Alt+Return keypress in a dirty tab", event: {altKey: true}, url: START_VALUE} + ]; + + for (let test of tests) { + info(`Running test: ${test.desc}`); + + // Add a new tab. + let tab = yield* promiseOpenNewTab(test.url || "about:blank"); + + // Trigger a load and check it occurs in the current tab. + let tabSwitchedPromise = promiseNewTabSwitched(); + triggerCommand(test.click || false, test.event || {}); + yield tabSwitchedPromise; + + // Check the load occurred in a new tab. + info("URL should be loaded in a new focused tab"); + is(gURLBar.inputField.value, TEST_VALUE, "Urlbar still has the value we entered"); + yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser); + is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused"); + isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab"); + + // Cleanup. + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); + } +}); + +function triggerCommand(shouldClick, event) { + gURLBar.value = TEST_VALUE; + gURLBar.focus(); + + if (shouldClick) { + is(gURLBar.getAttribute("pageproxystate"), "invalid", + "page proxy state must be invalid for go button to be visible"); + + let goButton = document.getElementById("urlbar-go-button"); + EventUtils.synthesizeMouseAtCenter(goButton, event); + } else { + EventUtils.synthesizeKey("VK_RETURN", event); + } +} + +function promiseLoadStarted() { + return new Promise(resolve => { + gBrowser.addTabsProgressListener({ + onStateChange(browser, webProgress, req, flags, status) { + if (flags & Ci.nsIWebProgressListener.STATE_START) { + gBrowser.removeTabsProgressListener(this); + resolve(); + } + } + }); + }); +} + +function* promiseOpenNewTab(url = "about:blank") { + let tab = gBrowser.addTab(url); + let tabSwitchPromise = promiseNewTabSwitched(tab); + gBrowser.selectedTab = tab; + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + yield tabSwitchPromise; + return tab; +} + +function promiseNewTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener("TabSwitchDone", function onSwitch() { + gBrowser.removeEventListener("TabSwitchDone", onSwitch); + executeSoon(resolve); + }); + }); +} + +function promiseCheckChildNoFocusedElement(browser) +{ + if (!gMultiProcessBrowser) { + Assert.equal(Services.focus.focusedElement, null, "There should be no focused element"); + return null; + } + + return ContentTask.spawn(browser, { }, function* () { + const fm = Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager); + Assert.equal(fm.focusedElement, null, "There should be no focused element"); + }); +} diff --git a/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js new file mode 100644 index 000000000..31fc84768 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const url = "data:text/html,<body>hi"; + +add_task(function*() { + yield* testURL(url, urlEnter); + yield* testURL(url, urlClick); +}); + +function urlEnter(url) { + gURLBar.value = url; + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); +} + +function urlClick(url) { + gURLBar.value = url; + gURLBar.focus(); + let goButton = document.getElementById("urlbar-go-button"); + EventUtils.synthesizeMouseAtCenter(goButton, {}); +} + +function promiseNewTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener("TabSwitchDone", function onSwitch() { + gBrowser.removeEventListener("TabSwitchDone", onSwitch); + executeSoon(resolve); + }); + }); +} + +function* testURL(url, loadFunc, endFunc) { + let tabSwitchedPromise = promiseNewTabSwitched(); + let tab = gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + + let pageshowPromise = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); + + yield tabSwitchedPromise; + yield pageshowPromise; + + let pagePrincipal = gBrowser.contentPrincipal; + loadFunc(url); + + yield BrowserTestUtils.waitForContentEvent(browser, "pageshow"); + + yield ContentTask.spawn(browser, { isRemote: gMultiProcessBrowser }, + function* (arg) { + const fm = Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager); + Assert.equal(fm.focusedElement, null, "focusedElement not null"); + + if (arg.isRemote) { + Assert.equal(fm.activeWindow, content, "activeWindow not correct"); + } + }); + + is(document.activeElement, browser, "content window should be focused"); + + ok(!gBrowser.contentPrincipal.equals(pagePrincipal), + "load of " + url + " by " + loadFunc.name + " should produce a page with a different principal"); + + gBrowser.removeTab(tab); +} diff --git a/browser/base/content/test/urlbar/browser_moz_action_link.js b/browser/base/content/test/urlbar/browser_moz_action_link.js new file mode 100644 index 000000000..ed2d36ee5 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_moz_action_link.js @@ -0,0 +1,31 @@ +"use strict"; + +const kURIs = [ + "moz-action:foo,", + "moz-action:foo", +]; + +add_task(function*() { + for (let uri of kURIs) { + let dataURI = `data:text/html,<a id=a href="${uri}" target=_blank>Link</a>`; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI); + + let tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, function() {}); + yield ContentTask.spawn(tab.linkedBrowser, null, function*() { + content.document.getElementById("a").click(); + }); + yield tabSwitchPromise; + isnot(gBrowser.selectedTab, tab, "Switched to new tab!"); + is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank"); + let newTab = gBrowser.selectedTab; + yield BrowserTestUtils.switchTab(gBrowser, tab); + yield BrowserTestUtils.switchTab(gBrowser, newTab); + is(gBrowser.selectedTab, newTab, "Switched to new tab again!"); + is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank after tab switch"); + // Finally, check that directly setting it produces the right results, too: + URLBarSetURI(makeURI(uri)); + is(gURLBar.value, "about:blank", "URL bar should still be displaying about:blank"); + yield BrowserTestUtils.removeTab(newTab); + yield BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js new file mode 100644 index 000000000..e9ba8d989 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js @@ -0,0 +1,49 @@ +function test() { + waitForExplicitFinish(); + testNext(); +} + +var pairs = [ + ["javascript:", ""], + ["javascript:1+1", "1+1"], + ["javascript:document.domain", "document.domain"], + ["data:text/html,<body>hi</body>", "data:text/html,<body>hi</body>"], + // Nested things get confusing because some things don't parse as URIs: + ["javascript:javascript:alert('hi!')", "alert('hi!')"], + ["data:data:text/html,<body>hi</body>", "data:data:text/html,<body>hi</body>"], + ["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"], + ["javascript:data:text/html,javascript:alert('hi!')", "data:text/html,javascript:alert('hi!')"], + ["data:data:text/html,javascript:alert('hi!')", "data:data:text/html,javascript:alert('hi!')"], +]; + +var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); + +function paste(input, cb) { + waitForClipboard(input, function() { + clipboardHelper.copyString(input); + }, function() { + document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste"); + cb(); + }, function() { + ok(false, "Failed to copy string '" + input + "' to clipboard"); + cb(); + }); +} + +function testNext() { + gURLBar.value = ''; + if (!pairs.length) { + finish(); + return; + } + + let [inputValue, expectedURL] = pairs.shift(); + + gURLBar.focus(); + paste(inputValue, function() { + is(gURLBar.textValue, expectedURL, "entering '" + inputValue + "' strips relevant bits."); + + setTimeout(testNext, 0); + }); +} + diff --git a/browser/base/content/test/urlbar/browser_search_favicon.js b/browser/base/content/test/urlbar/browser_search_favicon.js new file mode 100644 index 000000000..a8e6dbbcd --- /dev/null +++ b/browser/base/content/test/urlbar/browser_search_favicon.js @@ -0,0 +1,52 @@ +var gOriginalEngine; +var gEngine; +var gRestyleSearchesPref = "browser.urlbar.restyleSearches"; + +registerCleanupFunction(() => { + Services.prefs.clearUserPref(gRestyleSearchesPref); + Services.search.currentEngine = gOriginalEngine; + Services.search.removeEngine(gEngine); + return PlacesTestUtils.clearHistory(); +}); + +add_task(function*() { + Services.prefs.setBoolPref(gRestyleSearchesPref, true); +}); + +add_task(function*() { + + Services.search.addEngineWithDetails("SearchEngine", "", "", "", + "GET", "http://s.example.com/search"); + gEngine = Services.search.getEngineByName("SearchEngine"); + gEngine.addParam("q", "{searchTerms}", null); + gOriginalEngine = Services.search.currentEngine; + Services.search.currentEngine = gEngine; + + let uri = NetUtil.newURI("http://s.example.com/search?q=foo&client=1"); + yield PlacesTestUtils.addVisits({ uri: uri, title: "Foo - SearchEngine Search" }); + + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla"); + + // The first autocomplete result has the action searchengine, while + // the second result is the "search favicon" element. + yield promiseAutocompleteResultPopup("foo"); + let result = gURLBar.popup.richlistbox.children[1]; + + isnot(result, null, "Expect a search result"); + is(result.getAttribute("type"), "searchengine", "Expect correct `type` attribute"); + + let titleHbox = result._titleText.parentNode.parentNode; + ok(titleHbox.classList.contains("ac-title"), "Title hbox sanity check"); + is_element_visible(titleHbox, "Title element should be visible"); + + let urlHbox = result._urlText.parentNode.parentNode; + ok(urlHbox.classList.contains("ac-url"), "URL hbox sanity check"); + is_element_hidden(urlHbox, "URL element should be hidden"); + + let actionHbox = result._actionText.parentNode.parentNode; + ok(actionHbox.classList.contains("ac-action"), "Action hbox sanity check"); + is_element_hidden(actionHbox, "Action element should be hidden because it is not selected"); + is(result._actionText.textContent, "Search with SearchEngine", "Action text should be as expected"); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js new file mode 100644 index 000000000..d207092d4 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js @@ -0,0 +1,216 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim:set ts=2 sw=2 sts=2 et: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +requestLongerTimeout(2); + +const TEST_URL_BASES = [ + "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#tabmatch", + "http://example.org/browser/browser/base/content/test/urlbar/moz.png#tabmatch" +]; + +var gController = Cc["@mozilla.org/autocomplete/controller;1"]. + getService(Ci.nsIAutoCompleteController); + +var gTabCounter = 0; + +add_task(function* step_1() { + info("Running step 1"); + let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults"); + let promises = []; + for (let i = 0; i < maxResults - 1; i++) { + let tab = gBrowser.addTab(); + promises.push(loadTab(tab, TEST_URL_BASES[0] + (++gTabCounter))); + } + + yield Promise.all(promises); + yield ensure_opentabs_match_db(); +}); + +add_task(function* step_2() { + info("Running step 2"); + gBrowser.selectTabAtIndex(1); + gBrowser.removeCurrentTab(); + gBrowser.selectTabAtIndex(1); + gBrowser.removeCurrentTab(); + + let promises = []; + for (let i = 1; i < gBrowser.tabs.length; i++) + promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[1] + (++gTabCounter))); + + yield Promise.all(promises); + yield ensure_opentabs_match_db(); +}); + +add_task(function* step_3() { + info("Running step 3"); + let promises = []; + for (let i = 1; i < gBrowser.tabs.length; i++) + promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter)); + + yield Promise.all(promises); + yield ensure_opentabs_match_db(); +}); + +add_task(function* step_4() { + info("Running step 4 - ensure we don't register subframes as open pages"); + let tab = gBrowser.addTab(); + tab.linkedBrowser.loadURI('data:text/html,<body><iframe src=""></iframe></body>'); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + yield ContentTask.spawn(tab.linkedBrowser, null, function* () { + let iframe_loaded = ContentTaskUtils.waitForEvent(content.document, "load", true); + content.document.querySelector("iframe").src = "http://test2.example.org/"; + yield iframe_loaded; + }); + + yield ensure_opentabs_match_db(); +}); + +add_task(function* step_5() { + info("Running step 5 - remove tab immediately"); + let tab = gBrowser.addTab("about:logo"); + yield BrowserTestUtils.removeTab(tab); + yield ensure_opentabs_match_db(); +}); + +add_task(function* step_6() { + info("Running step 6 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result"); + let tabToKeep = gBrowser.addTab(); + let tab = gBrowser.addTab(); + tab.linkedBrowser.loadURI("about:mozilla"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + gBrowser.updateBrowserRemoteness(tabToKeep.linkedBrowser, tab.linkedBrowser.isRemoteBrowser); + gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab); + + yield ensure_opentabs_match_db() + + yield BrowserTestUtils.removeTab(tabToKeep); + + yield ensure_opentabs_match_db(); +}); + +add_task(function* step_7() { + info("Running step 7 - close all tabs"); + + Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); + + gBrowser.addTab("about:blank", {skipAnimation: true}); + while (gBrowser.tabs.length > 1) { + info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec); + gBrowser.selectTabAtIndex(0); + gBrowser.removeCurrentTab(); + } + + yield ensure_opentabs_match_db(); +}); + +add_task(function* cleanup() { + info("Cleaning up"); + + yield PlacesTestUtils.clearHistory(); +}); + +function loadTab(tab, url) { + // Because adding visits is async, we will not be notified immediately. + let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + let visited = new Promise(resolve => { + Services.obs.addObserver( + function observer(aSubject, aTopic, aData) { + if (url != aSubject.QueryInterface(Ci.nsIURI).spec) + return; + Services.obs.removeObserver(observer, aTopic); + resolve(); + }, + "uri-visit-saved", + false + ); + }); + + info("Loading page: " + url); + tab.linkedBrowser.loadURI(url); + return Promise.all([ loaded, visited ]); +} + +function ensure_opentabs_match_db() { + var tabs = {}; + + var winEnum = Services.wm.getEnumerator("navigator:browser"); + while (winEnum.hasMoreElements()) { + let browserWin = winEnum.getNext(); + // skip closed-but-not-destroyed windows + if (browserWin.closed) + continue; + + for (let i = 0; i < browserWin.gBrowser.tabContainer.childElementCount; i++) { + let browser = browserWin.gBrowser.getBrowserAtIndex(i); + let url = browser.currentURI.spec; + if (browserWin.isBlankPageURL(url)) + continue; + if (!(url in tabs)) + tabs[url] = 1; + else + tabs[url]++; + } + } + + return new Promise(resolve => { + checkAutocompleteResults(tabs, resolve); + }); +} + +function checkAutocompleteResults(aExpected, aCallback) +{ + gController.input = { + timeout: 10, + textValue: "", + searches: ["unifiedcomplete"], + searchParam: "enable-actions", + popupOpen: false, + minResultsForPopup: 0, + invalidate: function() {}, + disableAutoComplete: false, + completeDefaultIndex: false, + get popup() { return this; }, + onSearchBegin: function() {}, + onSearchComplete: function () + { + info("Found " + gController.matchCount + " matches."); + // Check to see the expected uris and titles match up (in any order) + for (let i = 0; i < gController.matchCount; i++) { + if (gController.getStyleAt(i).includes("heuristic")) { + info("Skip heuristic match"); + continue; + } + let action = gURLBar.popup.input._parseActionUrl(gController.getValueAt(i)); + let uri = action.params.url; + + info("Search for '" + uri + "' in open tabs."); + let expected = uri in aExpected; + ok(expected, uri + " was found in autocomplete, was " + (expected ? "" : "not ") + "expected"); + // Remove the found entry from expected results. + delete aExpected[uri]; + } + + // Make sure there is no reported open page that is not open. + for (let entry in aExpected) { + ok(false, "'" + entry + "' should be found in autocomplete"); + } + + executeSoon(aCallback); + }, + setSelectedIndex: function() {}, + get searchCount() { return this.searches.length; }, + getSearchAt: function(aIndex) { return this.searches[aIndex]; }, + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIAutoCompleteInput, + Ci.nsIAutoCompletePopup, + ]) + }; + + info("Searching open pages."); + gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage")); +} diff --git a/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js new file mode 100644 index 000000000..08a18b38a --- /dev/null +++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js @@ -0,0 +1,84 @@ +let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + +add_task(function*() { + let normalWindow = yield BrowserTestUtils.openNewBrowserWindow(); + let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + yield runTest(normalWindow, privateWindow, false); + yield BrowserTestUtils.closeWindow(normalWindow); + yield BrowserTestUtils.closeWindow(privateWindow); + + normalWindow = yield BrowserTestUtils.openNewBrowserWindow(); + privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + yield runTest(privateWindow, normalWindow, false); + yield BrowserTestUtils.closeWindow(normalWindow); + yield BrowserTestUtils.closeWindow(privateWindow); + + privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true}); + yield runTest(privateWindow, privateWindow, false); + yield BrowserTestUtils.closeWindow(privateWindow); + + normalWindow = yield BrowserTestUtils.openNewBrowserWindow(); + yield runTest(normalWindow, normalWindow, true); + yield BrowserTestUtils.closeWindow(normalWindow); +}); + +function* runTest(aSourceWindow, aDestWindow, aExpectSwitch, aCallback) { + yield BrowserTestUtils.openNewForegroundTab(aSourceWindow.gBrowser, testURL); + let testTab = yield BrowserTestUtils.openNewForegroundTab(aDestWindow.gBrowser); + + info("waiting for focus on the window"); + yield SimpleTest.promiseFocus(aDestWindow); + info("got focus on the window"); + + // Select the testTab + aDestWindow.gBrowser.selectedTab = testTab; + + // Ensure that this tab has no history entries + let sessionHistoryCount = yield new Promise(resolve => { + SessionStore.getSessionHistory(gBrowser.selectedTab, function(sessionHistory) { + resolve(sessionHistory.entries.length); + }); + }); + + ok(sessionHistoryCount < 2, + `The test tab has 1 or fewer history entries. sessionHistoryCount=${sessionHistoryCount}`); + // Ensure that this tab is on about:blank + is(testTab.linkedBrowser.currentURI.spec, "about:blank", + "The test tab is on about:blank"); + // Ensure that this tab's document has no child nodes + yield ContentTask.spawn(testTab.linkedBrowser, null, function*() { + ok(!content.document.body.hasChildNodes(), + "The test tab has no child nodes"); + }); + ok(!testTab.hasAttribute("busy"), + "The test tab doesn't have the busy attribute"); + + // Wait for the Awesomebar popup to appear. + yield promiseAutocompleteResultPopup(testURL, aDestWindow); + + info(`awesomebar popup appeared. aExpectSwitch: ${aExpectSwitch}`); + // Make sure the last match is selected. + let {controller, popup} = aDestWindow.gURLBar; + while (popup.selectedIndex < controller.matchCount - 1) { + info("handling key navigation for DOM_VK_DOWN key"); + controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN); + } + + let awaitTabSwitch; + if (aExpectSwitch) { + awaitTabSwitch = BrowserTestUtils.removeTab(testTab, {dontRemove: true}) + } + + // Execute the selected action. + controller.handleEnter(true); + info("sent Enter command to the controller"); + + if (aExpectSwitch) { + // If we expect a tab switch then the current tab + // will be closed and we switch to the other tab. + yield awaitTabSwitch; + } else { + // If we don't expect a tab switch then wait for the tab to load. + yield BrowserTestUtils.browserLoaded(testTab.linkedBrowser); + } +} diff --git a/browser/base/content/test/urlbar/browser_urlHighlight.js b/browser/base/content/test/urlbar/browser_urlHighlight.js new file mode 100644 index 000000000..ba1537d91 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlHighlight.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function testVal(aExpected) { + gURLBar.value = aExpected.replace(/[<>]/g, ""); + + let selectionController = gURLBar.editor.selectionController; + let selection = selectionController.getSelection(selectionController.SELECTION_URLSECONDARY); + let value = gURLBar.editor.rootElement.textContent; + let result = ""; + for (let i = 0; i < selection.rangeCount; i++) { + let range = selection.getRangeAt(i).toString(); + let pos = value.indexOf(range); + result += value.substring(0, pos) + "<" + range + ">"; + value = value.substring(pos + range.length); + } + result += value; + is(result, aExpected, + "Correct part of the urlbar contents is highlighted"); +} + +function test() { + const prefname = "browser.urlbar.formatting.enabled"; + + registerCleanupFunction(function () { + Services.prefs.clearUserPref(prefname); + URLBarSetURI(); + }); + + Services.prefs.setBoolPref(prefname, true); + + gURLBar.focus(); + + testVal("https://mozilla.org"); + + gBrowser.selectedBrowser.focus(); + + testVal("<https://>mozilla.org"); + testVal("<https://>mözilla.org"); + testVal("<https://>mozilla.imaginatory"); + + testVal("<https://www.>mozilla.org"); + testVal("<https://sub.>mozilla.org"); + testVal("<https://sub1.sub2.sub3.>mozilla.org"); + testVal("<www.>mozilla.org"); + testVal("<sub.>mozilla.org"); + testVal("<sub1.sub2.sub3.>mozilla.org"); + testVal("<mozilla.com.>mozilla.com"); + testVal("<https://mozilla.com:mozilla.com@>mozilla.com"); + testVal("<mozilla.com:mozilla.com@>mozilla.com"); + + testVal("<ftp.>mozilla.org"); + testVal("<ftp://ftp.>mozilla.org"); + + testVal("<https://sub.>mozilla.org"); + testVal("<https://sub1.sub2.sub3.>mozilla.org"); + testVal("<https://user:pass@sub1.sub2.sub3.>mozilla.org"); + testVal("<https://user:pass@>mozilla.org"); + testVal("<user:pass@sub1.sub2.sub3.>mozilla.org"); + testVal("<user:pass@>mozilla.org"); + + testVal("<https://>mozilla.org< >"); + testVal("mozilla.org< >"); + + testVal("<https://>mozilla.org</file.ext>"); + testVal("<https://>mozilla.org</sub/file.ext>"); + testVal("<https://>mozilla.org</sub/file.ext?foo>"); + testVal("<https://>mozilla.org</sub/file.ext?foo&bar>"); + testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>"); + testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>"); + testVal("foo.bar<?q=test>"); + testVal("foo.bar<#mozilla.org>"); + testVal("foo.bar<?somewhere.mozilla.org>"); + testVal("foo.bar<?@mozilla.org>"); + testVal("foo.bar<#x@mozilla.org>"); + testVal("foo.bar<#@x@mozilla.org>"); + testVal("foo.bar<?x@mozilla.org>"); + testVal("foo.bar<?@x@mozilla.org>"); + testVal("<foo.bar@x@>mozilla.org"); + testVal("<foo.bar@:baz@>mozilla.org"); + testVal("<foo.bar:@baz@>mozilla.org"); + testVal("<foo.bar@:ba:z@>mozilla.org"); + testVal("<foo.:bar:@baz@>mozilla.org"); + + testVal("<https://sub.>mozilla.org<:666/file.ext>"); + testVal("<sub.>mozilla.org<:666/file.ext>"); + testVal("localhost<:666/file.ext>"); + + let IPs = ["192.168.1.1", + "[::]", + "[::1]", + "[1::]", + "[::]", + "[::1]", + "[1::]", + "[1:2:3:4:5:6:7::]", + "[::1:2:3:4:5:6:7]", + "[1:2:a:B:c:D:e:F]", + "[1::8]", + "[1:2::8]", + "[fe80::222:19ff:fe11:8c76]", + "[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]", + "[::192.168.1.1]", + "[1::0.0.0.0]", + "[1:2::255.255.255.255]", + "[1:2:3::255.255.255.255]", + "[1:2:3:4::255.255.255.255]", + "[1:2:3:4:5::255.255.255.255]", + "[1:2:3:4:5:6:255.255.255.255]"]; + IPs.forEach(function (IP) { + testVal(IP); + testVal(IP + "</file.ext>"); + testVal(IP + "<:666/file.ext>"); + testVal("<https://>" + IP); + testVal("<https://>" + IP + "</file.ext>"); + testVal("<https://user:pass@>" + IP + "<:666/file.ext>"); + testVal("<user:pass@>" + IP + "<:666/file.ext>"); + }); + + testVal("mailto:admin@mozilla.org"); + testVal("gopher://mozilla.org/"); + testVal("about:config"); + testVal("jar:http://mozilla.org/example.jar!/"); + testVal("view-source:http://mozilla.org/"); + testVal("foo9://mozilla.org/"); + testVal("foo+://mozilla.org/"); + testVal("foo.://mozilla.org/"); + testVal("foo-://mozilla.org/"); + + Services.prefs.setBoolPref(prefname, false); + + testVal("https://mozilla.org"); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js new file mode 100644 index 000000000..792826eb1 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js @@ -0,0 +1,104 @@ +"use strict"; + +const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + +/** + * Test what happens if loading a URL that should clear the + * location bar after a parent process URL. + */ +add_task(function* clearURLBarAfterParentProcessURL() { + let tab = yield new Promise(resolve => { + gBrowser.selectedTab = gBrowser.addTab("about:preferences"); + let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab); + newTabBrowser.addEventListener("Initialized", function onInit() { + newTabBrowser.removeEventListener("Initialized", onInit, true); + resolve(gBrowser.selectedTab); + }, true); + }); + document.getElementById("home-button").click(); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + is(gURLBar.value, "", "URL bar should be empty"); + is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue"); + yield BrowserTestUtils.removeTab(tab); +}); + +/** + * Same as above, but open the tab without passing the URL immediately + * which changes behaviour in tabbrowser.xml. + */ +add_task(function* clearURLBarAfterParentProcessURLInExistingTab() { + let tab = yield new Promise(resolve => { + gBrowser.selectedTab = gBrowser.addTab(); + let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab); + newTabBrowser.addEventListener("Initialized", function onInit() { + newTabBrowser.removeEventListener("Initialized", onInit, true); + resolve(gBrowser.selectedTab); + }, true); + newTabBrowser.loadURI("about:preferences"); + }); + document.getElementById("home-button").click(); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + is(gURLBar.value, "", "URL bar should be empty"); + is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue"); + yield BrowserTestUtils.removeTab(tab); +}); + +/** + * Load about:home directly from an about:newtab page. Because it is an + * 'initial' page, we need to treat this specially if the user actually + * loads a page like this from the URL bar. + */ +add_task(function* clearURLBarAfterManuallyLoadingAboutHome() { + let promiseTabOpenedAndSwitchedTo = BrowserTestUtils.switchTab(gBrowser, () => {}); + // This opens about:newtab: + BrowserOpenTab(); + let tab = yield promiseTabOpenedAndSwitchedTo; + is(gURLBar.value, "", "URL bar should be empty"); + is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null"); + + gURLBar.value = "about:home"; + gURLBar.select(); + let aboutHomeLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:home"); + EventUtils.sendKey("return"); + yield aboutHomeLoaded; + + is(gURLBar.value, "", "URL bar should be empty"); + is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null"); + yield BrowserTestUtils.removeTab(tab); +}); + +/** + * Ensure we don't show 'about:home' in the URL bar temporarily in new tabs + * while we're switching remoteness (when the URL we're loading and the + * default content principal are different). + */ +add_task(function* dontTemporarilyShowAboutHome() { + yield SpecialPowers.pushPrefEnv({set: [["browser.startup.page", 1]]}); + let windowOpenedPromise = BrowserTestUtils.waitForNewWindow(); + let win = OpenBrowserWindow(); + yield windowOpenedPromise; + let promiseTabSwitch = BrowserTestUtils.switchTab(win.gBrowser, () => {}); + win.BrowserOpenTab(); + yield promiseTabSwitch; + yield TabStateFlusher.flush(win.gBrowser.selectedBrowser); + yield BrowserTestUtils.closeWindow(win); + ok(SessionStore.getClosedWindowCount(), "Should have a closed window"); + + windowOpenedPromise = BrowserTestUtils.waitForNewWindow(); + win = SessionStore.undoCloseWindow(0); + yield windowOpenedPromise; + let wpl = { + onLocationChange(wpl, request, location, flags) { + is(win.gURLBar.value, "", "URL bar value should stay empty."); + }, + }; + win.gBrowser.addProgressListener(wpl); + let otherTab = win.gBrowser.selectedTab.previousSibling; + let tabLoaded = BrowserTestUtils.browserLoaded(otherTab.linkedBrowser, false, "about:home"); + yield BrowserTestUtils.switchTab(win.gBrowser, otherTab); + yield tabLoaded; + win.gBrowser.removeProgressListener(wpl); + is(win.gURLBar.value, "", "URL bar value should be empty."); + + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js new file mode 100644 index 000000000..8101c101d --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js @@ -0,0 +1,49 @@ +// This test ensures that autoFilled values are not trimmed, unless the user +// selects from the autocomplete popup. + +add_task(function* setup() { + const PREF_TRIMURL = "browser.urlbar.trimURLs"; + const PREF_AUTOFILL = "browser.urlbar.autoFill"; + + registerCleanupFunction(function* () { + Services.prefs.clearUserPref(PREF_TRIMURL); + Services.prefs.clearUserPref(PREF_AUTOFILL); + yield PlacesTestUtils.clearHistory(); + gURLBar.handleRevert(); + }); + Services.prefs.setBoolPref(PREF_TRIMURL, true); + Services.prefs.setBoolPref(PREF_AUTOFILL, true); + + // Adding a tab would hit switch-to-tab, so it's safer to just add a visit. + yield PlacesTestUtils.addVisits({ + uri: "http://www.autofilltrimurl.com/whatever", + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }); +}); + +function* promiseSearch(searchtext) { + gURLBar.focus(); + gURLBar.inputField.value = searchtext.substr(0, searchtext.length -1); + EventUtils.synthesizeKey(searchtext.substr(-1, 1), {}); + yield promiseSearchComplete(); +} + +add_task(function* () { + yield promiseSearch("http://"); + is(gURLBar.inputField.value, "http://", "Autofilled value is as expected"); +}); + +add_task(function* () { + yield promiseSearch("http://au"); + is(gURLBar.inputField.value, "http://autofilltrimurl.com/", "Autofilled value is as expected"); +}); + +add_task(function* () { + yield promiseSearch("http://www.autofilltrimurl.com"); + is(gURLBar.inputField.value, "http://www.autofilltrimurl.com/", "Autofilled value is as expected"); + + // Now ensure selecting from the popup correctly trims. + is(gURLBar.controller.matchCount, 2, "Found the expected number of matches"); + EventUtils.synthesizeKey("VK_DOWN", {}); + is(gURLBar.inputField.value, "www.autofilltrimurl.com/whatever", "trim was applied correctly"); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarCopying.js b/browser/base/content/test/urlbar/browser_urlbarCopying.js new file mode 100644 index 000000000..8d5562b61 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js @@ -0,0 +1,232 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const trimPref = "browser.urlbar.trimURLs"; +const phishyUserPassPref = "network.http.phishy-userpass-length"; + +function toUnicode(input) { + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + + return converter.ConvertToUnicode(input); +} + +function test() { + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + + registerCleanupFunction(function () { + gBrowser.removeTab(tab); + Services.prefs.clearUserPref(trimPref); + Services.prefs.clearUserPref(phishyUserPassPref); + URLBarSetURI(); + }); + + Services.prefs.setBoolPref(trimPref, true); + Services.prefs.setIntPref(phishyUserPassPref, 32); // avoid prompting about phishing + + waitForExplicitFinish(); + + nextTest(); +} + +var tests = [ + // pageproxystate="invalid" + { + setURL: "http://example.com/", + expectedURL: "example.com", + copyExpected: "example.com" + }, + { + copyVal: "<e>xample.com", + copyExpected: "e" + }, + + // pageproxystate="valid" from this point on (due to the load) + { + loadURL: "http://example.com/", + expectedURL: "example.com", + copyExpected: "http://example.com/" + }, + { + copyVal: "<example.co>m", + copyExpected: "example.co" + }, + { + copyVal: "e<x>ample.com", + copyExpected: "x" + }, + { + copyVal: "<e>xample.com", + copyExpected: "e" + }, + + { + loadURL: "http://example.com/foo", + expectedURL: "example.com/foo", + copyExpected: "http://example.com/foo" + }, + { + copyVal: "<example.com>/foo", + copyExpected: "http://example.com" + }, + { + copyVal: "<example>.com/foo", + copyExpected: "example" + }, + + // Test that userPass is stripped out + { + loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass", + expectedURL: "mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass", + copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass" + }, + + // Test escaping + { + loadURL: "http://example.com/()%28%29%C3%A9", + expectedURL: "example.com/()()\xe9", + copyExpected: "http://example.com/()%28%29%C3%A9" + }, + { + copyVal: "<example.com/(>)()\xe9", + copyExpected: "http://example.com/(" + }, + { + copyVal: "e<xample.com/(>)()\xe9", + copyExpected: "xample.com/(" + }, + + { + loadURL: "http://example.com/%C3%A9%C3%A9", + expectedURL: "example.com/\xe9\xe9", + copyExpected: "http://example.com/%C3%A9%C3%A9" + }, + { + copyVal: "e<xample.com/\xe9>\xe9", + copyExpected: "xample.com/\xe9" + }, + { + copyVal: "<example.com/\xe9>\xe9", + copyExpected: "http://example.com/\xe9" + }, + + { + loadURL: "http://example.com/?%C3%B7%C3%B7", + expectedURL: "example.com/?\xf7\xf7", + copyExpected: "http://example.com/?%C3%B7%C3%B7" + }, + { + copyVal: "e<xample.com/?\xf7>\xf7", + copyExpected: "xample.com/?\xf7" + }, + { + copyVal: "<example.com/?\xf7>\xf7", + copyExpected: "http://example.com/?\xf7" + }, + { + loadURL: "http://example.com/a%20test", + expectedURL: "example.com/a test", + copyExpected: "http://example.com/a%20test" + }, + { + loadURL: "http://example.com/a%E3%80%80test", + expectedURL: toUnicode("example.com/a test"), + copyExpected: "http://example.com/a%E3%80%80test" + }, + { + loadURL: "http://example.com/a%20%C2%A0test", + expectedURL: "example.com/a%20%C2%A0test", + copyExpected: "http://example.com/a%20%C2%A0test" + }, + { + loadURL: "http://example.com/%20%20%20", + expectedURL: "example.com/%20%20%20", + copyExpected: "http://example.com/%20%20%20" + }, + { + loadURL: "http://example.com/%E3%80%80%E3%80%80", + expectedURL: "example.com/%E3%80%80%E3%80%80", + copyExpected: "http://example.com/%E3%80%80%E3%80%80" + }, + + // data: and javsacript: URIs shouldn't be encoded + { + loadURL: "javascript:('%C3%A9%20%25%50')", + expectedURL: "javascript:('%C3%A9 %25P')", + copyExpected: "javascript:('%C3%A9 %25P')" + }, + { + copyVal: "<javascript:(>'%C3%A9 %25P')", + copyExpected: "javascript:(" + }, + + { + loadURL: "data:text/html,(%C3%A9%20%25%50)", + expectedURL: "data:text/html,(%C3%A9 %25P)", + copyExpected: "data:text/html,(%C3%A9 %25P)", + }, + { + copyVal: "<data:text/html,(>%C3%A9 %25P)", + copyExpected: "data:text/html,(" + }, + { + copyVal: "<data:text/html,(%C3%A9 %25P>)", + copyExpected: "data:text/html,(%C3%A9 %25P", + } +]; + +function nextTest() { + let test = tests.shift(); + if (tests.length == 0) + runTest(test, finish); + else + runTest(test, nextTest); +} + +function runTest(test, cb) { + function doCheck() { + if (test.setURL || test.loadURL) { + gURLBar.valueIsTyped = !!test.setURL; + is(gURLBar.textValue, test.expectedURL, "url bar value set"); + } + + testCopy(test.copyVal, test.copyExpected, cb); + } + + if (test.loadURL) { + loadURL(test.loadURL, doCheck); + } else { + if (test.setURL) + gURLBar.value = test.setURL; + doCheck(); + } +} + +function testCopy(copyVal, targetValue, cb) { + info("Expecting copy of: " + targetValue); + waitForClipboard(targetValue, function () { + gURLBar.focus(); + if (copyVal) { + let startBracket = copyVal.indexOf("<"); + let endBracket = copyVal.indexOf(">"); + if (startBracket == -1 || endBracket == -1 || + startBracket > endBracket || + copyVal.replace("<", "").replace(">", "") != gURLBar.textValue) { + ok(false, "invalid copyVal: " + copyVal); + } + gURLBar.selectionStart = startBracket; + gURLBar.selectionEnd = endBracket - 1; + } else { + gURLBar.select(); + } + + goDoCommand("cmd_copy"); + }, cb, cb); +} + +function loadURL(aURL, aCB) { + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, aURL); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, aURL).then(aCB); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarDecode.js b/browser/base/content/test/urlbar/browser_urlbarDecode.js new file mode 100644 index 000000000..6a2c421ef --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarDecode.js @@ -0,0 +1,97 @@ +"use strict"; + +// This test makes sure (1) you can't break the urlbar by typing particular JSON +// or JS fragments into it, (2) urlbar.textValue shows URLs unescaped, and (3) +// the urlbar also shows the URLs embedded in action URIs unescaped. See bug +// 1233672. + +add_task(function* injectJSON() { + let inputStrs = [ + 'http://example.com/ ", "url": "bar', + 'http://example.com/\\', + 'http://example.com/"', + 'http://example.com/","url":"evil.com', + 'http://mozilla.org/\\u0020', + 'http://www.mozilla.org/","url":1e6,"some-key":"foo', + 'http://www.mozilla.org/","url":null,"some-key":"foo', + 'http://www.mozilla.org/","url":["foo","bar"],"some-key":"foo', + ]; + for (let inputStr of inputStrs) { + yield checkInput(inputStr); + } + gURLBar.value = ""; + gURLBar.handleRevert(); + gURLBar.blur(); +}); + +add_task(function losslessDecode() { + let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa"; + let url = "http://" + urlNoScheme; + gURLBar.textValue = url; + // Since this is directly setting textValue, it is expected to be trimmed. + Assert.equal(gURLBar.inputField.value, urlNoScheme, + "The string displayed in the textbox should not be escaped"); + gURLBar.value = ""; + gURLBar.handleRevert(); + gURLBar.blur(); +}); + +add_task(function* actionURILosslessDecode() { + let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa"; + let url = "http://" + urlNoScheme; + yield promiseAutocompleteResultPopup(url); + + // At this point the heuristic result is selected but the urlbar's value is + // simply `url`. Key down and back around until the heuristic result is + // selected again, and at that point the urlbar's value should be a visiturl + // moz-action. + + do { + gURLBar.controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN); + } while (gURLBar.popup.selectedIndex != 0); + + let [, type, ] = gURLBar.value.match(/^moz-action:([^,]+),(.*)$/); + Assert.equal(type, "visiturl", + "visiturl action URI should be in the urlbar"); + + Assert.equal(gURLBar.inputField.value, urlNoScheme, + "The string displayed in the textbox should not be escaped"); + + gURLBar.value = ""; + gURLBar.handleRevert(); + gURLBar.blur(); +}); + +function* checkInput(inputStr) { + yield promiseAutocompleteResultPopup(inputStr); + + let item = gURLBar.popup.richlistbox.firstChild; + Assert.ok(item, "Should have a result"); + + // visiturl matches have their param.urls fixed up. + let fixupInfo = Services.uriFixup.getFixupURIInfo(inputStr, + Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS | + Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP + ); + let expectedVisitURL = fixupInfo.fixedURI.spec; + + let type = "visiturl"; + let params = { + url: expectedVisitURL, + input: inputStr, + }; + for (let key in params) { + params[key] = encodeURIComponent(params[key]); + } + let expectedURL = "moz-action:" + type + "," + JSON.stringify(params); + Assert.equal(item.getAttribute("url"), expectedURL, "url"); + + Assert.equal(item.getAttribute("title"), inputStr.replace("\\", "/"), "title"); + Assert.equal(item.getAttribute("text"), inputStr, "text"); + + let itemType = item.getAttribute("type"); + Assert.equal(itemType, "visiturl"); + + Assert.equal(item._titleText.textContent, inputStr.replace("\\", "/"), "Visible title"); + Assert.equal(item._actionText.textContent, "Visit", "Visible action"); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarDelete.js b/browser/base/content/test/urlbar/browser_urlbarDelete.js new file mode 100644 index 000000000..d4eb6c856 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarDelete.js @@ -0,0 +1,39 @@ +add_task(function*() { + let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://bug1105244.example.com/", + title: "test" }); + + registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.remove(bm); + }); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, testDelete); +}); + +function sendHome() { + // unclear why VK_HOME doesn't work on Mac, but it doesn't... + if (Services.appinfo.OS == "Darwin") { + EventUtils.synthesizeKey("VK_LEFT", { altKey: true }); + } else { + EventUtils.synthesizeKey("VK_HOME", {}); + } +} + +function sendDelete() { + EventUtils.synthesizeKey("VK_DELETE", {}); +} + +function* testDelete() { + yield promiseAutocompleteResultPopup("bug1105244"); + + // move to the start. + sendHome(); + // delete the first few chars - each delete should operate on the input field. + sendDelete(); + Assert.equal(gURLBar.inputField.value, "ug1105244"); + + yield promisePopupShown(gURLBar.popup); + + sendDelete(); + Assert.equal(gURLBar.inputField.value, "g1105244"); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarEnter.js b/browser/base/content/test/urlbar/browser_urlbarEnter.js new file mode 100644 index 000000000..32cbaf2be --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarEnter.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_VALUE = "example.com/\xF7?\xF7"; +const START_VALUE = "example.com/%C3%B7?%C3%B7"; + +add_task(function* () { + info("Simple return keypress"); + let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE); + + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + // Check url bar and selected tab. + is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress"); + is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab"); + + // Cleanup. + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(function* () { + info("Alt+Return keypress"); + // due to bug 691608, we must wait for the load event, else isTabEmpty() will + // return true on e10s for this tab, so it will be reused even with altKey. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE); + + let tabOpenPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {altKey: true}); + + // wait for the new tab to appear. + yield tabOpenPromise; + + // Check url bar and selected tab. + is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress"); + isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab"); + + // Cleanup. + yield BrowserTestUtils.removeTab(tab); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js b/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js new file mode 100644 index 000000000..22e336f91 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js @@ -0,0 +1,69 @@ +function repeat(limit, func) { + for (let i = 0; i < limit; i++) { + func(i); + } +} + +function* promiseAutoComplete(inputText) { + gURLBar.focus(); + gURLBar.value = inputText.slice(0, -1); + EventUtils.synthesizeKey(inputText.slice(-1), {}); + yield promiseSearchComplete(); +} + +function is_selected(index) { + is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`); +} + +let gMaxResults; + +add_task(function*() { + registerCleanupFunction(function* () { + yield PlacesTestUtils.clearHistory(); + }); + + yield PlacesTestUtils.clearHistory(); + + gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults"); + + let visits = []; + repeat(gMaxResults, i => { + visits.push({ + uri: makeURI("http://example.com/autocomplete/?" + i), + }); + }); + yield PlacesTestUtils.addVisits(visits); + + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield promiseAutoComplete("http://example.com/autocomplete/"); + + let popup = gURLBar.popup; + let results = popup.richlistbox.children; + is(results.length, gMaxResults, + "Should get gMaxResults=" + gMaxResults + " results"); + + let initiallySelected = gURLBar.popup.richlistbox.selectedIndex; + + info("Key Down to select the next item"); + EventUtils.synthesizeKey("VK_DOWN", {}); + is_selected(initiallySelected + 1); + let expectedURL = gURLBar.controller.getFinalCompleteValueAt(initiallySelected + 1); + + is(gURLBar.value, gURLBar.controller.getValueAt(initiallySelected + 1), + "Value in the URL bar should be updated by keyboard selection"); + + // Verify that what we're about to do changes the selectedIndex: + isnot(initiallySelected + 1, 3, "Shouldn't be changing the selectedIndex to the same index we keyboard-selected."); + + // Would love to use a synthetic mousemove event here, but that doesn't seem to do anything. + // EventUtils.synthesizeMouseAtCenter(results[3], {type: "mousemove"}); + gURLBar.popup.richlistbox.selectedIndex = 3; + is_selected(3); + + let autocompletePopupHidden = promisePopupHidden(gURLBar.popup); + let openedExpectedPage = waitForDocLoadAndStopIt(expectedURL); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield Promise.all([autocompletePopupHidden, openedExpectedPage]); + + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js new file mode 100644 index 000000000..8c9e2c9f2 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. +* http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function*() { + // Remove the search bar from toolbar + CustomizableUI.removeWidgetFromArea("search-container"); + + // Test that Ctrl/Cmd + K will focus the url bar + let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus"); + EventUtils.synthesizeKey("k", { accelKey: true }); + yield focusPromise; + Assert.equal(document.activeElement, gURLBar.inputField, "URL Bar should be focused"); + + // Reset changes made to toolbar + CustomizableUI.reset(); +}); + diff --git a/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js b/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js new file mode 100644 index 000000000..152106dad --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js @@ -0,0 +1,111 @@ +"use strict"; + +/** + * Check that navigating through both the URL bar and using in-page hash- or ref- + * based links and back or forward navigation updates the URL bar and identity block correctly. + */ +add_task(function* () { + let baseURL = "https://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + let url = baseURL + "#foo"; + yield BrowserTestUtils.withNewTab({ gBrowser, url }, function*(browser) { + let identityBox = document.getElementById("identity-box"); + let expectedURL = url; + + let verifyURLBarState = testType => { + is(gURLBar.textValue, expectedURL, "URL bar visible value should be correct " + testType); + is(gURLBar.value, expectedURL, "URL bar value should be correct " + testType); + ok(identityBox.classList.contains("verifiedDomain"), "Identity box should know we're doing SSL " + testType); + is(gURLBar.getAttribute("pageproxystate"), "valid", "URL bar is in valid page proxy state"); + }; + + verifyURLBarState("at the beginning"); + + let locationChangePromise; + let resolveLocationChangePromise; + let expectURL = url => { + expectedURL = url; + locationChangePromise = new Promise(r => resolveLocationChangePromise = r); + }; + let wpl = { + onLocationChange(wpl, request, location, flags) { + is(location.spec, expectedURL, "Got the expected URL"); + resolveLocationChangePromise(); + }, + }; + gBrowser.addProgressListener(wpl); + + expectURL(baseURL + "#foo"); + gURLBar.select(); + EventUtils.sendKey("return"); + + yield locationChangePromise; + verifyURLBarState("after hitting enter on the same URL a second time"); + + expectURL(baseURL + "#bar"); + gURLBar.value = expectedURL; + gURLBar.select(); + EventUtils.sendKey("return"); + + yield locationChangePromise; + verifyURLBarState("after a URL bar hash navigation"); + + expectURL(baseURL + "#foo"); + yield ContentTask.spawn(browser, null, function() { + let a = content.document.createElement("a"); + a.href = "#foo"; + a.textContent = "Foo Link"; + content.document.body.appendChild(a); + a.click(); + }); + + yield locationChangePromise; + verifyURLBarState("after a page link hash navigation"); + + expectURL(baseURL + "#bar"); + gBrowser.goBack(); + + yield locationChangePromise; + verifyURLBarState("after going back"); + + expectURL(baseURL + "#foo"); + gBrowser.goForward(); + + yield locationChangePromise; + verifyURLBarState("after going forward"); + + expectURL(baseURL + "#foo"); + gURLBar.select(); + EventUtils.sendKey("return"); + + yield locationChangePromise; + verifyURLBarState("after hitting enter on the same URL"); + + gBrowser.removeProgressListener(wpl); + }); +}); + +/** + * Check that initial secure loads that swap remoteness + * get the correct page icon when finished. + */ +add_task(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false); + // NB: CPOW usage because new tab pages can be preloaded, in which case no + // load events fire. + yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden) + let url = "https://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#foo"; + gURLBar.value = url; + gURLBar.select(); + EventUtils.sendKey("return"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + is(gURLBar.textValue, url, "URL bar visible value should be correct when the page loads from about:newtab"); + is(gURLBar.value, url, "URL bar value should be correct when the page loads from about:newtab"); + let identityBox = document.getElementById("identity-box"); + ok(identityBox.classList.contains("verifiedDomain"), + "Identity box should know we're doing SSL when the page loads from about:newtab"); + is(gURLBar.getAttribute("pageproxystate"), "valid", + "URL bar is in valid page proxy state when SSL page with hash loads from about:newtab"); + yield BrowserTestUtils.removeTab(tab); +}); + diff --git a/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js new file mode 100644 index 000000000..9c8996059 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js @@ -0,0 +1,49 @@ +"use strict"; + +/** + * Verify user typed text remains in the URL bar when tab switching, even when + * loads fail. + */ +add_task(function* () { + let input = "i-definitely-dont-exist.example.com"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false); + // NB: CPOW usage because new tab pages can be preloaded, in which case no + // load events fire. + yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden) + let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser); + gURLBar.value = input; + gURLBar.select(); + EventUtils.sendKey("return"); + yield errorPageLoaded; + is(gURLBar.textValue, input, "Text is still in URL bar"); + yield BrowserTestUtils.switchTab(gBrowser, tab.previousSibling); + yield BrowserTestUtils.switchTab(gBrowser, tab); + is(gURLBar.textValue, input, "Text is still in URL bar after tab switch"); + yield BrowserTestUtils.removeTab(tab); +}); + +/** + * Invalid URIs fail differently (that is, immediately, in the loadURI call) + * if keyword searches are turned off. Test that this works, too. + */ +add_task(function* () { + let input = "To be or not to be-that is the question"; + yield new Promise(resolve => SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]}, resolve)); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false); + // NB: CPOW usage because new tab pages can be preloaded, in which case no + // load events fire. + yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden) + let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser); + gURLBar.value = input; + gURLBar.select(); + EventUtils.sendKey("return"); + yield errorPageLoaded; + is(gURLBar.textValue, input, "Text is still in URL bar"); + is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser"); + yield BrowserTestUtils.switchTab(gBrowser, tab.previousSibling); + yield BrowserTestUtils.switchTab(gBrowser, tab); + is(gURLBar.textValue, input, "Text is still in URL bar after tab switch"); + is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser"); + yield BrowserTestUtils.removeTab(tab); +}); + diff --git a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js new file mode 100644 index 000000000..1f58b8edd --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js @@ -0,0 +1,232 @@ +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; + +let gMaxResults; + +add_task(function* init() { + Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true); + gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults"); + + // Add a search suggestion engine and move it to the front so that it appears + // as the first one-off. + let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + Services.search.moveEngine(engine, 0); + + registerCleanupFunction(function* () { + yield hidePopup(); + yield PlacesTestUtils.clearHistory(); + }); + + yield PlacesTestUtils.clearHistory(); + + let visits = []; + for (let i = 0; i < gMaxResults; i++) { + visits.push({ + uri: makeURI("http://example.com/browser_urlbarOneOffs.js/?" + i), + // TYPED so that the visit shows up when the urlbar's drop-down arrow is + // pressed. + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }); + } + yield PlacesTestUtils.addVisits(visits); +}); + +// Keys up and down through the history panel, i.e., the panel that's shown when +// there's no text in the textbox. +add_task(function* history() { + gURLBar.focus(); + EventUtils.synthesizeKey("VK_DOWN", {}) + yield promisePopupShown(gURLBar.popup); + + assertState(-1, -1, ""); + + // Key down through each result. + for (let i = 0; i < gMaxResults; i++) { + EventUtils.synthesizeKey("VK_DOWN", {}) + assertState(i, -1, + "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1)); + } + + // Key down through each one-off. + let numButtons = + gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length; + for (let i = 0; i < numButtons; i++) { + EventUtils.synthesizeKey("VK_DOWN", {}) + assertState(-1, i, ""); + } + + // Key down once more. Nothing should be selected. + EventUtils.synthesizeKey("VK_DOWN", {}) + assertState(-1, -1, ""); + + // Once more. The first result should be selected. + EventUtils.synthesizeKey("VK_DOWN", {}) + assertState(0, -1, + "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - 1)); + + // Now key up. Nothing should be selected again. + EventUtils.synthesizeKey("VK_UP", {}) + assertState(-1, -1, ""); + + // Key up through each one-off. + for (let i = numButtons - 1; i >= 0; i--) { + EventUtils.synthesizeKey("VK_UP", {}) + assertState(-1, i, ""); + } + + // Key up through each result. + for (let i = gMaxResults - 1; i >= 0; i--) { + EventUtils.synthesizeKey("VK_UP", {}) + assertState(i, -1, + "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1)); + } + + // Key up once more. Nothing should be selected. + EventUtils.synthesizeKey("VK_UP", {}) + assertState(-1, -1, ""); + + yield hidePopup(); +}); + +// Keys up and down through the non-history panel, i.e., the panel that's shown +// when you type something in the textbox. +add_task(function* typedValue() { + // Use a typed value that returns the visits added above but that doesn't + // trigger autofill since that would complicate the test. + let typedValue = "browser_urlbarOneOffs"; + yield promiseAutocompleteResultPopup(typedValue, window, true); + + assertState(0, -1, typedValue); + + // Key down through each result. The first result is already selected, which + // is why gMaxResults - 1 is the correct number of times to do this. + for (let i = 0; i < gMaxResults - 1; i++) { + EventUtils.synthesizeKey("VK_DOWN", {}) + // i starts at zero so that the textValue passed to assertState is correct. + // But that means that i + 1 is the expected selected index, since initially + // (when this loop starts) the first result is selected. + assertState(i + 1, -1, + "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1)); + } + + // Key down through each one-off. + let numButtons = + gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length; + for (let i = 0; i < numButtons; i++) { + EventUtils.synthesizeKey("VK_DOWN", {}) + assertState(-1, i, typedValue); + } + + // Key down once more. The selection should wrap around to the first result. + EventUtils.synthesizeKey("VK_DOWN", {}) + assertState(0, -1, typedValue); + + // Now key up. The selection should wrap back around to the one-offs. Key + // up through all the one-offs. + for (let i = numButtons - 1; i >= 0; i--) { + EventUtils.synthesizeKey("VK_UP", {}) + assertState(-1, i, typedValue); + } + + // Key up through each non-heuristic result. + for (let i = gMaxResults - 2; i >= 0; i--) { + EventUtils.synthesizeKey("VK_UP", {}) + assertState(i + 1, -1, + "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1)); + } + + // Key up once more. The heuristic result should be selected. + EventUtils.synthesizeKey("VK_UP", {}) + assertState(0, -1, typedValue); + + yield hidePopup(); +}); + +// Checks that "Search with Current Search Engine" items are updated to "Search +// with One-Off Engine" when a one-off is selected. +add_task(function* searchWith() { + let typedValue = "foo"; + yield promiseAutocompleteResultPopup(typedValue); + + assertState(0, -1, typedValue); + + let item = gURLBar.popup.richlistbox.firstChild; + Assert.equal(item._actionText.textContent, + "Search with " + Services.search.currentEngine.name, + "Sanity check: first result's action text"); + + // Alt+Down to the first one-off. Now the first result and the first one-off + // should both be selected. + EventUtils.synthesizeKey("VK_DOWN", { altKey: true }) + assertState(0, 0, typedValue); + + let engineName = gURLBar.popup.oneOffSearchButtons.selectedButton.engine.name; + Assert.notEqual(engineName, Services.search.currentEngine.name, + "Sanity check: First one-off engine should not be " + + "the current engine"); + Assert.equal(item._actionText.textContent, + "Search with " + engineName, + "First result's action text should be updated"); + + yield hidePopup(); +}); + +// Clicks a one-off. +add_task(function* oneOffClick() { + gBrowser.selectedTab = gBrowser.addTab(); + + // We are explicitly using something that looks like a url, to make the test + // stricter. Even if it looks like a url, we should search. + let typedValue = "foo.bar"; + yield promiseAutocompleteResultPopup(typedValue); + + assertState(0, -1, typedValue); + + let oneOffs = gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true); + let resultsPromise = + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, + "http://mochi.test:8888/"); + EventUtils.synthesizeMouseAtCenter(oneOffs[0], {}); + yield resultsPromise; + + gBrowser.removeTab(gBrowser.selectedTab); +}); + +// Presses the Return key when a one-off is selected. +add_task(function* oneOffReturn() { + gBrowser.selectedTab = gBrowser.addTab(); + + // We are explicitly using something that looks like a url, to make the test + // stricter. Even if it looks like a url, we should search. + let typedValue = "foo.bar"; + yield promiseAutocompleteResultPopup(typedValue, window, true); + + assertState(0, -1, typedValue); + + // Alt+Down to select the first one-off. + EventUtils.synthesizeKey("VK_DOWN", { altKey: true }) + assertState(0, 0, typedValue); + + let resultsPromise = + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, + "http://mochi.test:8888/"); + EventUtils.synthesizeKey("VK_RETURN", {}) + yield resultsPromise; + + gBrowser.removeTab(gBrowser.selectedTab); +}); + + +function assertState(result, oneOff, textValue = undefined) { + Assert.equal(gURLBar.popup.selectedIndex, result, + "Expected result should be selected"); + Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, oneOff, + "Expected one-off should be selected"); + if (textValue !== undefined) { + Assert.equal(gURLBar.textValue, textValue, "Expected textValue"); + } +} + +function* hidePopup() { + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield promisePopupHidden(gURLBar.popup); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js b/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js new file mode 100644 index 000000000..5db0f0ea6 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js @@ -0,0 +1,41 @@ +"use strict"; + +/** + * Test that when opening a private browsing window and typing in it before about:privatebrowsing + * loads, we don't clear the URL bar. + */ +add_task(function*() { + let urlbarTestValue = "Mary had a little lamb"; + let win = OpenBrowserWindow({private: true}); + yield BrowserTestUtils.waitForEvent(win, "load"); + let urlbar = win.document.getElementById("urlbar"); + urlbar.value = urlbarTestValue; + // Need this so the autocomplete controller attaches: + let focusEv = new FocusEvent("focus", {}); + urlbar.dispatchEvent(focusEv); + // And so we know input happened: + let inputEv = new InputEvent("input", {data: "", view: win, bubbles: true}); + urlbar.onInput(inputEv); + // Check it worked: + is(urlbar.value, urlbarTestValue, "URL bar value should be there"); + is(win.gBrowser.selectedBrowser.userTypedValue, urlbarTestValue, "browser object should know the url bar value"); + + let continueTest; + let continuePromise = new Promise(resolve => continueTest = resolve); + let wpl = { + onLocationChange(aWebProgress, aRequest, aLocation) { + if (aLocation && aLocation.spec == "about:privatebrowsing") { + continueTest(); + } + }, + }; + win.gBrowser.addProgressListener(wpl); + + yield continuePromise; + is(urlbar.value, urlbarTestValue, + "URL bar value should be the same once about:privatebrowsing has loaded"); + is(win.gBrowser.selectedBrowser.userTypedValue, urlbarTestValue, + "browser object should still know url bar value once about:privatebrowsing has loaded"); + win.gBrowser.removeProgressListener(wpl); + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js b/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js new file mode 100644 index 000000000..d66514c5a --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js @@ -0,0 +1,57 @@ +const kURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html"; + +function* addBookmark(bookmark) { + if (bookmark.keyword) { + yield PlacesUtils.keywords.insert({ + keyword: bookmark.keyword, + url: bookmark.url, + }); + } + + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: bookmark.url, + title: bookmark.title, + }); + + registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.remove(bm); + if (bookmark.keyword) { + yield PlacesUtils.keywords.remove(bookmark.keyword); + } + }); +} + +/** + * Check that if the user hits enter and ctrl-t at the same time, we open the URL in the right tab. + */ +add_task(function* hitEnterLoadInRightTab() { + info("Opening new tab"); + let oldTabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + BrowserOpenTab(); + let oldTab = (yield oldTabCreatedPromise).target; + let oldTabLoadedPromise = BrowserTestUtils.browserLoaded(oldTab.linkedBrowser, false, kURL); + oldTabLoadedPromise.then(() => info("Old tab loaded")); + let newTabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + + info("Creating bookmark and keyword"); + yield addBookmark({title: "Test for keyword bookmark and URL", url: kURL, keyword: "urlbarkeyword"}); + info("Filling URL bar, sending <return> and opening a tab"); + gURLBar.value = "urlbarkeyword"; + gURLBar.select(); + EventUtils.sendKey("return"); + BrowserOpenTab(); + info("Waiting for new tab"); + let newTab = (yield newTabCreatedPromise).target; + info("Created new tab; waiting for either tab to load"); + let newTabLoadedPromise = BrowserTestUtils.browserLoaded(newTab.linkedBrowser, false, kURL); + newTabLoadedPromise.then(() => info("New tab loaded")); + yield Promise.race([newTabLoadedPromise, oldTabLoadedPromise]); + is(newTab.linkedBrowser.currentURI.spec, "about:newtab", "New tab still has about:newtab"); + is(oldTab.linkedBrowser.currentURI.spec, kURL, "Old tab loaded URL"); + info("Closing new tab"); + yield BrowserTestUtils.removeTab(newTab); + info("Closing old tab"); + yield BrowserTestUtils.removeTab(oldTab); + info("Finished"); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarRevert.js b/browser/base/content/test/urlbar/browser_urlbarRevert.js new file mode 100644 index 000000000..0ce3c8fac --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarRevert.js @@ -0,0 +1,37 @@ +var tab = null; + +function test() { + waitForExplicitFinish(); + + let pageLoaded = { + onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { + gBrowser.removeProgressListener(this); + executeSoon(checkURLBarRevert); + } + } + } + + gBrowser.addProgressListener(pageLoaded); + tab = gBrowser.addTab("http://example.com"); + gBrowser.selectedTab = tab; +} + +function checkURLBarRevert() { + let originalValue = gURLBar.value; + + gBrowser.userTypedValue = "foobar"; + gBrowser.selectedTab = gBrowser.tabs[0]; + gBrowser.selectedTab = tab; + is(gURLBar.value, "foobar", "location bar displays typed value"); + + gURLBar.focus(); + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + + is(gURLBar.value, originalValue, "ESC reverted the location bar value"); + + gBrowser.removeTab(tab); + finish(); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js new file mode 100644 index 000000000..ee0342055 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js @@ -0,0 +1,198 @@ +"use strict"; + +var notificationObserver; +registerCleanupFunction(function() { + Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost"); + if (notificationObserver) { + notificationObserver.disconnect(); + } +}); + +function promiseNotification(aBrowser, value, expected, input) { + let deferred = Promise.defer(); + let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser); + if (expected) { + info("Waiting for " + value + " notification"); + let checkForNotification = function() { + if (notificationBox.getNotificationWithValue(value)) { + info("Saw the notification"); + notificationObserver.disconnect(); + notificationObserver = null; + deferred.resolve(); + } + } + if (notificationObserver) { + notificationObserver.disconnect(); + } + notificationObserver = new MutationObserver(checkForNotification); + notificationObserver.observe(notificationBox, {childList: true}); + } else { + setTimeout(() => { + is(notificationBox.getNotificationWithValue(value), null, + `We are expecting to not get a notification for ${input}`); + deferred.resolve(); + }, 1000); + } + return deferred.promise; +} + +function* runURLBarSearchTest({valueToOpen, expectSearch, expectNotification, aWindow=window}) { + aWindow.gURLBar.value = valueToOpen; + let expectedURI; + if (!expectSearch) { + expectedURI = "http://" + valueToOpen + "/"; + } else { + yield new Promise(resolve => { + Services.search.init(resolve); + }); + expectedURI = Services.search.defaultEngine.getSubmission(valueToOpen, null, "keyword").uri.spec; + } + aWindow.gURLBar.focus(); + let docLoadPromise = waitForDocLoadAndStopIt(expectedURI, aWindow.gBrowser.selectedBrowser); + EventUtils.synthesizeKey("VK_RETURN", {}, aWindow); + + yield Promise.all([ + docLoadPromise, + promiseNotification(aWindow.gBrowser, "keyword-uri-fixup", expectNotification, valueToOpen) + ]); +} + +add_task(function* test_navigate_full_domain() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "www.mozilla.org", + expectSearch: false, + expectNotification: false, + }); + gBrowser.removeTab(tab); +}); + +add_task(function* test_navigate_decimal_ip() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "1234", + expectSearch: true, + expectNotification: false, + }); + gBrowser.removeTab(tab); +}); + +add_task(function* test_navigate_decimal_ip_with_path() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "1234/12", + expectSearch: true, + expectNotification: false, + }); + gBrowser.removeTab(tab); +}); + +add_task(function* test_navigate_large_number() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "123456789012345", + expectSearch: true, + expectNotification: false + }); + gBrowser.removeTab(tab); +}); + +add_task(function* test_navigate_small_hex_number() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "0x1f00ffff", + expectSearch: true, + expectNotification: false + }); + gBrowser.removeTab(tab); +}); + +add_task(function* test_navigate_large_hex_number() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "0x7f0000017f000001", + expectSearch: true, + expectNotification: false + }); + gBrowser.removeTab(tab); +}); + +function get_test_function_for_localhost_with_hostname(hostName, isPrivate) { + return function* test_navigate_single_host() { + const pref = "browser.fixup.domainwhitelist.localhost"; + let win; + if (isPrivate) { + let promiseWin = BrowserTestUtils.waitForNewWindow(); + win = OpenBrowserWindow({private: true}); + yield promiseWin; + let deferredOpenFocus = Promise.defer(); + waitForFocus(deferredOpenFocus.resolve, win); + yield deferredOpenFocus.promise; + } else { + win = window; + } + let browser = win.gBrowser; + let tab = yield BrowserTestUtils.openNewForegroundTab(browser); + + Services.prefs.setBoolPref(pref, false); + yield* runURLBarSearchTest({ + valueToOpen: hostName, + expectSearch: true, + expectNotification: true, + aWindow: win, + }); + + let notificationBox = browser.getNotificationBox(tab.linkedBrowser); + let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup"); + let docLoadPromise = waitForDocLoadAndStopIt("http://" + hostName + "/", tab.linkedBrowser); + notification.querySelector(".notification-button-default").click(); + + // check pref value + let prefValue = Services.prefs.getBoolPref(pref); + is(prefValue, !isPrivate, "Pref should have the correct state."); + + yield docLoadPromise; + browser.removeTab(tab); + + // Now try again with the pref set. + tab = browser.selectedTab = browser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + // In a private window, the notification should appear again. + yield* runURLBarSearchTest({ + valueToOpen: hostName, + expectSearch: isPrivate, + expectNotification: isPrivate, + aWindow: win, + }); + browser.removeTab(tab); + if (isPrivate) { + info("Waiting for private window to close"); + yield BrowserTestUtils.closeWindow(win); + let deferredFocus = Promise.defer(); + info("Waiting for focus"); + waitForFocus(deferredFocus.resolve, window); + yield deferredFocus.promise; + } + } +} + +add_task(get_test_function_for_localhost_with_hostname("localhost")); +add_task(get_test_function_for_localhost_with_hostname("localhost.")); +add_task(get_test_function_for_localhost_with_hostname("localhost", true)); + +add_task(function* test_navigate_invalid_url() { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + yield* runURLBarSearchTest({ + valueToOpen: "mozilla is awesome", + expectSearch: true, + expectNotification: false, + }); + gBrowser.removeTab(tab); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js new file mode 100644 index 000000000..5146ba98c --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js @@ -0,0 +1,66 @@ +const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches"; +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; + +// Must run first. +add_task(function* prepare() { + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true); + let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + let oldCurrentEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + registerCleanupFunction(function* () { + Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF); + Services.search.currentEngine = oldCurrentEngine; + + // Clicking suggestions causes visits to search results pages, so clear that + // history now. + yield PlacesTestUtils.clearHistory(); + + // Make sure the popup is closed for the next test. + gURLBar.blur(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); + }); +}); + +add_task(function* clickSuggestion() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("foo"); + let [idx, suggestion, engineName] = yield promiseFirstSuggestion(); + Assert.equal(engineName, + "browser_searchSuggestionEngine%20searchSuggestionEngine.xml", + "Expected suggestion engine"); + let item = gURLBar.popup.richlistbox.getItemAtIndex(idx); + + let uri = Services.search.currentEngine.getSubmission(suggestion).uri; + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, + false, uri.spec); + item.click(); + yield loadPromise; + yield BrowserTestUtils.removeTab(tab); +}); + +function getFirstSuggestion() { + let controller = gURLBar.popup.input.controller; + let matchCount = controller.matchCount; + for (let i = 0; i < matchCount; i++) { + let url = controller.getValueAt(i); + let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/); + if (mozActionMatch) { + let [, type, paramStr] = mozActionMatch; + let params = JSON.parse(paramStr); + if (type == "searchengine" && "searchSuggestion" in params) { + return [i, params.searchSuggestion, params.engineName]; + } + } + } + return [-1, null, null]; +} + +function* promiseFirstSuggestion() { + let tuple = [-1, null, null]; + yield BrowserTestUtils.waitForCondition(() => { + tuple = getFirstSuggestion(); + return tuple[0] >= 0; + }); + return tuple; +} diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js new file mode 100644 index 000000000..94ae8a3ff --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js @@ -0,0 +1,254 @@ +const SUGGEST_ALL_PREF = "browser.search.suggest.enabled"; +const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches"; +const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice"; +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; + +// Must run first. +add_task(function* prepare() { + let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + let oldCurrentEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + registerCleanupFunction(function* () { + Services.search.currentEngine = oldCurrentEngine; + Services.prefs.clearUserPref(SUGGEST_ALL_PREF); + Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF); + + // Disable the notification for future tests so it doesn't interfere with + // them. clearUserPref() won't work because by default the pref is false. + yield setUserMadeChoicePref(true); + + // Make sure the popup is closed for the next test. + gURLBar.blur(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); + }); +}); + +add_task(function* focus() { + // Focusing the urlbar used to open the popup in order to show the + // notification, but it doesn't anymore. Make sure it does not. + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + yield setUserMadeChoicePref(false); + gURLBar.blur(); + gURLBar.focus(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed"); +}); + +add_task(function* dismissWithoutResults() { + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + yield setUserMadeChoicePref(false); + gURLBar.blur(); + gURLBar.focus(); + let popupPromise = promisePopupShown(gURLBar.popup); + gURLBar.openPopup(); + yield popupPromise; + Assert.ok(gURLBar.popup.popupOpen, "popup should be open"); + assertVisible(true); + Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results"); + let disableButton = document.getAnonymousElementByAttribute( + gURLBar.popup, "anonid", "search-suggestions-notification-disable" + ); + let transitionPromise = promiseTransition(); + disableButton.click(); + yield transitionPromise; + Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); + gURLBar.blur(); + gURLBar.focus(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed"); + yield promiseAutocompleteResultPopup("foo"); + Assert.ok(gURLBar.popup.popupOpen, "popup should be open"); + assertVisible(false); +}); + +add_task(function* dismissWithResults() { + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + yield setUserMadeChoicePref(false); + gURLBar.blur(); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("foo"); + Assert.ok(gURLBar.popup.popupOpen, "popup should be open"); + assertVisible(true); + Assert.ok(gURLBar.popup._matchCount > 0, "popup should have results"); + let disableButton = document.getAnonymousElementByAttribute( + gURLBar.popup, "anonid", "search-suggestions-notification-disable" + ); + let transitionPromise = promiseTransition(); + disableButton.click(); + yield transitionPromise; + Assert.ok(gURLBar.popup.popupOpen, "popup should remain open"); + gURLBar.blur(); + gURLBar.focus(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed"); + yield promiseAutocompleteResultPopup("foo"); + Assert.ok(gURLBar.popup.popupOpen, "popup should be open"); + assertVisible(false); +}); + +add_task(function* disable() { + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + yield setUserMadeChoicePref(false); + gURLBar.blur(); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("foo"); + Assert.ok(gURLBar.popup.popupOpen, "popup should be open"); + assertVisible(true); + let disableButton = document.getAnonymousElementByAttribute( + gURLBar.popup, "anonid", "search-suggestions-notification-disable" + ); + let transitionPromise = promiseTransition(); + disableButton.click(); + yield transitionPromise; + gURLBar.blur(); + yield promiseAutocompleteResultPopup("foo"); + Assert.ok(!suggestionsPresent()); +}); + +add_task(function* enable() { + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false); + yield setUserMadeChoicePref(false); + gURLBar.blur(); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("foo"); + assertVisible(true); + Assert.ok(!suggestionsPresent()); + let enableButton = document.getAnonymousElementByAttribute( + gURLBar.popup, "anonid", "search-suggestions-notification-enable" + ); + let searchPromise = BrowserTestUtils.waitForCondition(suggestionsPresent, + "waiting for suggestions"); + enableButton.click(); + yield searchPromise; + // Clicking Yes should trigger a new search so that suggestions appear + // immediately. + Assert.ok(suggestionsPresent()); + gURLBar.blur(); + gURLBar.focus(); + // Suggestions should still be present in a new search of course. + yield promiseAutocompleteResultPopup("bar"); + Assert.ok(suggestionsPresent()); +}); + +add_task(function* privateWindow() { + // Since suggestions are disabled in private windows, the notification should + // not appear even when suggestions are otherwise enabled. + let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true }); + win.gURLBar.blur(); + win.gURLBar.focus(); + yield promiseAutocompleteResultPopup("foo", win); + assertVisible(false, win); + win.gURLBar.blur(); + yield BrowserTestUtils.closeWindow(win); +}); + +add_task(function* multipleWindows() { + // Opening multiple windows, using their urlbars, and then dismissing the + // notification in one should dismiss the notification in all. + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false); + yield setUserMadeChoicePref(false); + + gURLBar.focus(); + yield promiseAutocompleteResultPopup("win1"); + assertVisible(true); + + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + win2.gURLBar.focus(); + yield promiseAutocompleteResultPopup("win2", win2); + assertVisible(true, win2); + + let win3 = yield BrowserTestUtils.openNewBrowserWindow(); + win3.gURLBar.focus(); + yield promiseAutocompleteResultPopup("win3", win3); + assertVisible(true, win3); + + let enableButton = win3.document.getAnonymousElementByAttribute( + win3.gURLBar.popup, "anonid", "search-suggestions-notification-enable" + ); + let transitionPromise = promiseTransition(win3); + enableButton.click(); + yield transitionPromise; + assertVisible(false, win3); + + win2.gURLBar.focus(); + yield promiseAutocompleteResultPopup("win2done", win2); + assertVisible(false, win2); + + gURLBar.focus(); + yield promiseAutocompleteResultPopup("win1done"); + assertVisible(false); + + yield BrowserTestUtils.closeWindow(win2); + yield BrowserTestUtils.closeWindow(win3); +}); + +add_task(function* enableOutsideNotification() { + // Setting the suggest.searches pref outside the notification (e.g., by + // ticking the checkbox in the preferences window) should hide it. + Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true); + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false); + yield setUserMadeChoicePref(false); + + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("foo"); + assertVisible(false); +}); + +/** + * Setting the choice pref triggers a pref observer in the urlbar, which hides + * the notification if it's present. This function returns a promise that's + * resolved once the observer fires. + * + * @param userMadeChoice A boolean, the pref's new value. + * @return A Promise that's resolved when the observer fires -- or, if the pref + * is currently the given value, that's resolved immediately. + */ +function setUserMadeChoicePref(userMadeChoice) { + return new Promise(resolve => { + let currentUserMadeChoice = Services.prefs.getBoolPref(CHOICE_PREF); + if (currentUserMadeChoice != userMadeChoice) { + Services.prefs.addObserver(CHOICE_PREF, function obs(subj, topic, data) { + Services.prefs.removeObserver(CHOICE_PREF, obs); + resolve(); + }, false); + } + Services.prefs.setBoolPref(CHOICE_PREF, userMadeChoice); + if (currentUserMadeChoice == userMadeChoice) { + resolve(); + } + }); +} + +function suggestionsPresent() { + let controller = gURLBar.popup.input.controller; + let matchCount = controller.matchCount; + for (let i = 0; i < matchCount; i++) { + let url = controller.getValueAt(i); + let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/); + if (mozActionMatch) { + let [, type, paramStr] = mozActionMatch; + let params = JSON.parse(paramStr); + if (type == "searchengine" && "searchSuggestion" in params) { + return true; + } + } + } + return false; +} + +function assertVisible(visible, win=window) { + let style = + win.getComputedStyle(win.gURLBar.popup.searchSuggestionsNotification); + Assert.equal(style.visibility, visible ? "visible" : "collapse"); +} + +function promiseTransition(win=window) { + return new Promise(resolve => { + win.gURLBar.popup.addEventListener("transitionend", function onEnd() { + win.gURLBar.popup.removeEventListener("transitionend", onEnd, true); + // The urlbar needs to handle the transitionend first, but that happens + // naturally since promises are resolved at the end of the current tick. + resolve(); + }, true); + }); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js new file mode 100644 index 000000000..8c28401ea --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js @@ -0,0 +1,216 @@ +"use strict"; + +Cu.import("resource:///modules/BrowserUITelemetry.jsm"); + +const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches"; +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; + +// Must run first. +add_task(function* prepare() { + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true); + let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + let oldCurrentEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + + registerCleanupFunction(function* () { + Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF); + Services.search.currentEngine = oldCurrentEngine; + + // Clicking urlbar results causes visits to their associated pages, so clear + // that history now. + yield PlacesTestUtils.clearHistory(); + + // Make sure the popup is closed for the next test. + gURLBar.blur(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); + }); + + // Move the mouse away from the urlbar one-offs so that a one-off engine is + // not inadvertently selected. + yield new Promise(resolve => { + EventUtils.synthesizeNativeMouseMove(window.document.documentElement, 0, 0, + resolve); + }); +}); + +add_task(function* heuristicResultMouse() { + yield compareCounts(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("heuristicResult"); + let action = getActionAtIndex(0); + Assert.ok(!!action, "there should be an action at index 0"); + Assert.equal(action.type, "searchengine", "type should be searchengine"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + gURLBar.popup.richlistbox.getItemAtIndex(0).click(); + yield loadPromise; + yield BrowserTestUtils.removeTab(tab); + }); +}); + +add_task(function* heuristicResultKeyboard() { + yield compareCounts(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("heuristicResult"); + let action = getActionAtIndex(0); + Assert.ok(!!action, "there should be an action at index 0"); + Assert.equal(action.type, "searchengine", "type should be searchengine"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + EventUtils.sendKey("return"); + yield loadPromise; + yield BrowserTestUtils.removeTab(tab); + }); +}); + +add_task(function* searchSuggestionMouse() { + yield compareCounts(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("searchSuggestion"); + let idx = getFirstSuggestionIndex(); + Assert.ok(idx >= 0, "there should be a first suggestion"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + gURLBar.popup.richlistbox.getItemAtIndex(idx).click(); + yield loadPromise; + yield BrowserTestUtils.removeTab(tab); + }); +}); + +add_task(function* searchSuggestionKeyboard() { + yield compareCounts(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + gURLBar.focus(); + yield promiseAutocompleteResultPopup("searchSuggestion"); + let idx = getFirstSuggestionIndex(); + Assert.ok(idx >= 0, "there should be a first suggestion"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + while (idx--) { + EventUtils.sendKey("down"); + } + EventUtils.sendKey("return"); + yield loadPromise; + yield BrowserTestUtils.removeTab(tab); + }); +}); + +/** + * This does three things: gets current telemetry/FHR counts, calls + * clickCallback, gets telemetry/FHR counts again to compare them to the old + * counts. + * + * @param clickCallback Use this to open the urlbar popup and choose and click a + * result. + */ +function* compareCounts(clickCallback) { + // Search events triggered by clicks (not the Return key in the urlbar) are + // recorded in three places: + // * BrowserUITelemetry + // * Telemetry histogram named "SEARCH_COUNTS" + // * FHR + + let engine = Services.search.currentEngine; + let engineID = "org.mozilla.testsearchsuggestions"; + + // First, get the current counts. + + // BrowserUITelemetry + let uiTelemCount = 0; + let bucket = BrowserUITelemetry.currentBucket; + let events = BrowserUITelemetry.getToolbarMeasures().countableEvents; + if (events[bucket] && + events[bucket].search && + events[bucket].search.urlbar) { + uiTelemCount = events[bucket].search.urlbar; + } + + // telemetry histogram SEARCH_COUNTS + let histogramCount = 0; + let histogramKey = engineID + ".urlbar"; + let histogram; + try { + histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS"); + } catch (ex) { + // No searches performed yet, not a problem. + } + if (histogram) { + let snapshot = histogram.snapshot(); + if (histogramKey in snapshot) { + histogramCount = snapshot[histogramKey].sum; + } + } + + // FHR -- first make sure the engine has an identifier so that FHR is happy. + Object.defineProperty(engine.wrappedJSObject, "identifier", + { value: engineID }); + + gURLBar.focus(); + yield clickCallback(); + + // Now get the new counts and compare them to the old. + + // BrowserUITelemetry + events = BrowserUITelemetry.getToolbarMeasures().countableEvents; + Assert.ok(bucket in events, "bucket should be recorded"); + events = events[bucket]; + Assert.ok("search" in events, "search should be recorded"); + events = events.search; + Assert.ok("urlbar" in events, "urlbar should be recorded"); + Assert.equal(events.urlbar, uiTelemCount + 1, + "clicked suggestion should be recorded"); + + // telemetry histogram SEARCH_COUNTS + histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS"); + let snapshot = histogram.snapshot(); + Assert.ok(histogramKey in snapshot, "histogram with key should be recorded"); + Assert.equal(snapshot[histogramKey].sum, histogramCount + 1, + "histogram sum should be incremented"); +} + +/** + * Returns the "action" object at the given index in the urlbar results: + * { type, params: {}} + * + * @param index The index in the urlbar results. + * @return An action object, or null if index >= number of results. + */ +function getActionAtIndex(index) { + let controller = gURLBar.popup.input.controller; + if (controller.matchCount <= index) { + return null; + } + let url = controller.getValueAt(index); + let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/); + if (!mozActionMatch) { + let msg = "result at index " + index + " is not a moz-action: " + url; + Assert.ok(false, msg); + throw new Error(msg); + } + let [, type, paramStr] = mozActionMatch; + return { + type: type, + params: JSON.parse(paramStr), + }; +} + +/** + * Returns the index of the first search suggestion in the urlbar results. + * + * @return An index, or -1 if there are no search suggestions. + */ +function getFirstSuggestionIndex() { + let controller = gURLBar.popup.input.controller; + let matchCount = controller.matchCount; + for (let i = 0; i < matchCount; i++) { + let url = controller.getValueAt(i); + let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/); + if (mozActionMatch) { + let [, type, paramStr] = mozActionMatch; + let params = JSON.parse(paramStr); + if (type == "searchengine" && "searchSuggestion" in params) { + return i; + } + } + } + return -1; +} diff --git a/browser/base/content/test/urlbar/browser_urlbarStop.js b/browser/base/content/test/urlbar/browser_urlbarStop.js new file mode 100644 index 000000000..8cf9d8017 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarStop.js @@ -0,0 +1,30 @@ +"use strict"; + +const goodURL = "http://mochi.test:8888/"; +const badURL = "http://mochi.test:8888/whatever.html"; + +add_task(function* () { + gBrowser.selectedTab = gBrowser.addTab(goodURL); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + is(gURLBar.textValue, gURLBar.trimValue(goodURL), "location bar reflects loaded page"); + + yield typeAndSubmitAndStop(badURL); + is(gURLBar.textValue, gURLBar.trimValue(goodURL), "location bar reflects loaded page after stop()"); + gBrowser.removeCurrentTab(); + + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + is(gURLBar.textValue, "", "location bar is empty"); + + yield typeAndSubmitAndStop(badURL); + is(gURLBar.textValue, gURLBar.trimValue(badURL), "location bar reflects stopped page in an empty tab"); + gBrowser.removeCurrentTab(); +}); + +function* typeAndSubmitAndStop(url) { + yield promiseAutocompleteResultPopup(url, window, true); + is(gURLBar.textValue, gURLBar.trimValue(url), "location bar reflects loading page"); + + let promise = waitForDocLoadAndStopIt(url, gBrowser.selectedBrowser, false); + gURLBar.handleCommand(); + yield promise; +} diff --git a/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js b/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js new file mode 100644 index 000000000..913e99a8e --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js @@ -0,0 +1,98 @@ +add_task(function* () { + const PREF_TRIMURLS = "browser.urlbar.trimURLs"; + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + registerCleanupFunction(function* () { + yield BrowserTestUtils.removeTab(tab); + Services.prefs.clearUserPref(PREF_TRIMURLS); + URLBarSetURI(); + }); + + Services.prefs.setBoolPref(PREF_TRIMURLS, true); + + testVal("http://mozilla.org/", "mozilla.org"); + testVal("https://mozilla.org/", "https://mozilla.org"); + testVal("http://mözilla.org/", "mözilla.org"); + testVal("http://mozilla.imaginatory/", "mozilla.imaginatory"); + testVal("http://www.mozilla.org/", "www.mozilla.org"); + testVal("http://sub.mozilla.org/", "sub.mozilla.org"); + testVal("http://sub1.sub2.sub3.mozilla.org/", "sub1.sub2.sub3.mozilla.org"); + testVal("http://mozilla.org/file.ext", "mozilla.org/file.ext"); + testVal("http://mozilla.org/sub/", "mozilla.org/sub/"); + + testVal("http://ftp.mozilla.org/", "ftp.mozilla.org"); + testVal("http://ftp1.mozilla.org/", "ftp1.mozilla.org"); + testVal("http://ftp42.mozilla.org/", "ftp42.mozilla.org"); + testVal("http://ftpx.mozilla.org/", "ftpx.mozilla.org"); + testVal("ftp://ftp.mozilla.org/", "ftp://ftp.mozilla.org"); + testVal("ftp://ftp1.mozilla.org/", "ftp://ftp1.mozilla.org"); + testVal("ftp://ftp42.mozilla.org/", "ftp://ftp42.mozilla.org"); + testVal("ftp://ftpx.mozilla.org/", "ftp://ftpx.mozilla.org"); + + testVal("https://user:pass@mozilla.org/", "https://user:pass@mozilla.org"); + testVal("https://user@mozilla.org/", "https://user@mozilla.org"); + testVal("http://user:pass@mozilla.org/", "user:pass@mozilla.org"); + testVal("http://user@mozilla.org/", "user@mozilla.org"); + testVal("http://sub.mozilla.org:666/", "sub.mozilla.org:666"); + + testVal("https://[fe80::222:19ff:fe11:8c76]/file.ext"); + testVal("http://[fe80::222:19ff:fe11:8c76]/", "[fe80::222:19ff:fe11:8c76]"); + testVal("https://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext"); + testVal("http://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext", "user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext"); + + testVal("mailto:admin@mozilla.org"); + testVal("gopher://mozilla.org/"); + testVal("about:config"); + testVal("jar:http://mozilla.org/example.jar!/"); + testVal("view-source:http://mozilla.org/"); + + // Behaviour for hosts with no dots depends on the whitelist: + let fixupWhitelistPref = "browser.fixup.domainwhitelist.localhost"; + Services.prefs.setBoolPref(fixupWhitelistPref, false); + testVal("http://localhost"); + Services.prefs.setBoolPref(fixupWhitelistPref, true); + testVal("http://localhost", "localhost"); + Services.prefs.clearUserPref(fixupWhitelistPref); + + testVal("http:// invalid url"); + + testVal("http://someotherhostwithnodots"); + testVal("http://localhost/ foo bar baz"); + testVal("http://localhost.localdomain/ foo bar baz", "localhost.localdomain/ foo bar baz"); + + Services.prefs.setBoolPref(PREF_TRIMURLS, false); + + testVal("http://mozilla.org/"); + + Services.prefs.setBoolPref(PREF_TRIMURLS, true); + + let promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, + false, "http://example.com/"); + gBrowser.loadURI("http://example.com/"); + yield promiseLoaded; + + yield testCopy("example.com", "http://example.com/") + + SetPageProxyState("invalid"); + gURLBar.valueIsTyped = true; + yield testCopy("example.com", "example.com"); +}); + +function testVal(originalValue, targetValue) { + gURLBar.value = originalValue; + gURLBar.valueIsTyped = false; + is(gURLBar.textValue, targetValue || originalValue, "url bar value set"); +} + +function testCopy(originalValue, targetValue) { + return new Promise((resolve, reject) => { + waitForClipboard(targetValue, function () { + is(gURLBar.textValue, originalValue, "url bar copy value set"); + + gURLBar.focus(); + gURLBar.select(); + goDoCommand("cmd_copy"); + }, resolve, reject); + }); +} diff --git a/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js b/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js new file mode 100644 index 000000000..c3cdf507f --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js @@ -0,0 +1,17 @@ +"use strict"; + +/** + * Disable keyword.enabled (so no keyword search), and check that when you type in + * "example" and hit enter, the browser loads and the URL bar is updated accordingly. + */ +add_task(function* () { + yield new Promise(resolve => SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]}, resolve)); + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) { + gURLBar.value = "example"; + gURLBar.select(); + let loadPromise = BrowserTestUtils.browserLoaded(browser, false, url => url == "http://www.example.com/"); + EventUtils.sendKey("return"); + yield loadPromise; + is(gURLBar.textValue, "www.example.com"); + }); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js b/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js new file mode 100644 index 000000000..7fefd3f77 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js @@ -0,0 +1,146 @@ +/* This test ensures that backspacing autoFilled values still allows to + * confirm the remaining value. + */ + +function* test_autocomplete(data) { + let {desc, typed, autofilled, modified, keys, action, onAutoFill} = data; + info(desc); + + yield promiseAutocompleteResultPopup(typed); + is(gURLBar.textValue, autofilled, "autofilled value is as expected"); + if (onAutoFill) + onAutoFill() + + keys.forEach(key => EventUtils.synthesizeKey(key, {})); + + is(gURLBar.textValue, modified, "backspaced value is as expected"); + + yield promiseSearchComplete(); + + ok(gURLBar.popup.richlistbox.children.length > 0, "Should get at least 1 result"); + let result = gURLBar.popup.richlistbox.children[0]; + let type = result.getAttribute("type"); + let types = type.split(/\s+/); + ok(types.indexOf(action) >= 0, `The type attribute "${type}" includes the expected action "${action}"`); + + gURLBar.popup.hidePopup(); + yield promisePopupHidden(gURLBar.popup); + gURLBar.blur(); +} + +add_task(function* () { + registerCleanupFunction(function* () { + Services.prefs.clearUserPref("browser.urlbar.autoFill"); + gURLBar.handleRevert(); + yield PlacesTestUtils.clearHistory(); + }); + Services.prefs.setBoolPref("browser.urlbar.autoFill", true); + + // Add a typed visit, so it will be autofilled. + yield PlacesTestUtils.addVisits({ + uri: NetUtil.newURI("http://example.com/"), + transition: Ci.nsINavHistoryService.TRANSITION_TYPED + }); + + yield test_autocomplete({ desc: "DELETE the autofilled part should search", + typed: "exam", + autofilled: "example.com/", + modified: "exam", + keys: ["VK_DELETE"], + action: "searchengine" + }); + yield test_autocomplete({ desc: "DELETE the final slash should visit", + typed: "example.com", + autofilled: "example.com/", + modified: "example.com", + keys: ["VK_DELETE"], + action: "visiturl" + }); + + yield test_autocomplete({ desc: "BACK_SPACE the autofilled part should search", + typed: "exam", + autofilled: "example.com/", + modified: "exam", + keys: ["VK_BACK_SPACE"], + action: "searchengine" + }); + yield test_autocomplete({ desc: "BACK_SPACE the final slash should visit", + typed: "example.com", + autofilled: "example.com/", + modified: "example.com", + keys: ["VK_BACK_SPACE"], + action: "visiturl" + }); + + yield test_autocomplete({ desc: "DELETE the autofilled part, then BACK_SPACE, should search", + typed: "exam", + autofilled: "example.com/", + modified: "exa", + keys: ["VK_DELETE", "VK_BACK_SPACE"], + action: "searchengine" + }); + yield test_autocomplete({ desc: "DELETE the final slash, then BACK_SPACE, should search", + typed: "example.com", + autofilled: "example.com/", + modified: "example.co", + keys: ["VK_DELETE", "VK_BACK_SPACE"], + action: "visiturl" + }); + + yield test_autocomplete({ desc: "BACK_SPACE the autofilled part, then BACK_SPACE, should search", + typed: "exam", + autofilled: "example.com/", + modified: "exa", + keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"], + action: "searchengine" + }); + yield test_autocomplete({ desc: "BACK_SPACE the final slash, then BACK_SPACE, should search", + typed: "example.com", + autofilled: "example.com/", + modified: "example.co", + keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"], + action: "visiturl" + }); + + yield test_autocomplete({ desc: "BACK_SPACE after blur should search", + typed: "ex", + autofilled: "example.com/", + modified: "e", + keys: ["VK_BACK_SPACE"], + action: "searchengine", + onAutoFill: () => { + gURLBar.blur(); + gURLBar.focus(); + gURLBar.selectionStart = 1; + gURLBar.selectionEnd = 12; + } + }); + yield test_autocomplete({ desc: "DELETE after blur should search", + typed: "ex", + autofilled: "example.com/", + modified: "e", + keys: ["VK_DELETE"], + action: "searchengine", + onAutoFill: () => { + gURLBar.blur(); + gURLBar.focus(); + gURLBar.selectionStart = 1; + gURLBar.selectionEnd = 12; + } + }); + yield test_autocomplete({ desc: "double BACK_SPACE after blur should search", + typed: "ex", + autofilled: "example.com/", + modified: "e", + keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"], + action: "searchengine", + onAutoFill: () => { + gURLBar.blur(); + gURLBar.focus(); + gURLBar.selectionStart = 2; + gURLBar.selectionEnd = 12; + } + }); + + yield PlacesTestUtils.clearHistory(); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbar_blanking.js b/browser/base/content/test/urlbar/browser_urlbar_blanking.js new file mode 100644 index 000000000..13660edab --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbar_blanking.js @@ -0,0 +1,35 @@ +"use strict"; + +add_task(function*() { + for (let page of gInitialPages) { + if (page == "about:newtab") { + // New tab preloading makes this a pain to test, so skip + continue; + } + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, page); + ok(!gURLBar.value, "The URL bar should be empty if we load a plain " + page + " page."); + yield BrowserTestUtils.removeTab(tab); + } +}); + +add_task(function*() { + const URI = "http://www.example.com/browser/browser/base/content/test/urlbar/file_blank_but_not_blank.html"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URI); + is(gURLBar.value, URI, "The URL bar should match the URI"); + let browserLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + ContentTask.spawn(tab.linkedBrowser, null, function() { + content.document.querySelector('a').click(); + }); + yield browserLoaded; + ok(gURLBar.value.startsWith("javascript"), "The URL bar should have the JS URI"); + // When reloading, the javascript: uri we're using will throw an exception. + // That's deliberate, so we need to tell mochitest to ignore it: + SimpleTest.expectUncaughtException(true); + yield ContentTask.spawn(tab.linkedBrowser, null, function*() { + // This is sync, so by the time we return we should have changed the URL bar. + content.location.reload(); + }); + ok(!!gURLBar.value, "URL bar should not be blank."); + yield BrowserTestUtils.removeTab(tab); + SimpleTest.expectUncaughtException(false); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js b/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js new file mode 100644 index 000000000..63ed58a62 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js @@ -0,0 +1,41 @@ +"use strict"; + +function* checkURLBarValueStays(browser) { + gURLBar.select(); + EventUtils.synthesizeKey("a", {}); + is(gURLBar.value, "a", "URL bar value should match after sending a key"); + yield new Promise(resolve => { + let listener = { + onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { + ok(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT, + "Should only get a same document location change"); + gBrowser.selectedBrowser.removeProgressListener(filter); + filter = null; + resolve(); + }, + }; + let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] + .createInstance(Ci.nsIWebProgress); + filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL); + gBrowser.selectedBrowser.addProgressListener(filter); + }); + is(gURLBar.value, "a", "URL bar should not have been changed by location changes."); +} + +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "http://example.com/browser/browser/base/content/test/urlbar/file_urlbar_edit_dos.html" + }, function*(browser) { + yield ContentTask.spawn(browser, "", function() { + content.wrappedJSObject.dos_hash(); + }); + yield checkURLBarValueStays(browser); + yield ContentTask.spawn(browser, "", function() { + content.clearTimeout(content.wrappedJSObject.dos_timeout); + content.wrappedJSObject.dos_pushState(); + }); + yield checkURLBarValueStays(browser); + }); +}); + diff --git a/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js new file mode 100644 index 000000000..9a1df0505 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js @@ -0,0 +1,39 @@ +"use strict"; + +/** + * Verify that when loading and going back/forward through history between URLs + * loaded in the content process, and URLs loaded in the parent process, we + * don't set the URL for the tab to about:blank inbetween the loads. + */ +add_task(function*() { + let url = "http://www.example.com/foo.html"; + yield BrowserTestUtils.withNewTab({gBrowser, url}, function*(browser) { + let wpl = { + onLocationChange(wpl, request, location, flags) { + if (location.schemeIs("about")) { + is(location.spec, "about:config", "Only about: location change should be for about:preferences"); + } else { + is(location.spec, url, "Only non-about: location change should be for the http URL we're dealing with."); + } + }, + }; + gBrowser.addProgressListener(wpl); + + let didLoad = BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) { + return loadedURL == "about:config"; + }); + yield BrowserTestUtils.loadURI(browser, "about:config"); + yield didLoad; + + gBrowser.goBack(); + yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) { + return url == loadedURL; + }); + gBrowser.goForward(); + yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) { + return loadedURL == "about:config"; + }); + gBrowser.removeProgressListener(wpl); + }); +}); + diff --git a/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js b/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js new file mode 100644 index 000000000..04b1c508b --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function*() { + let button = document.getElementById("urlbar-search-settings"); + if (!button) { + ok("Skipping test"); + return; + } + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* () { + let popupopened = BrowserTestUtils.waitForEvent(gURLBar.popup, "popupshown"); + + gURLBar.focus(); + EventUtils.synthesizeKey("a", {}); + yield popupopened; + + // Since the current tab is blank the preferences pane will load there + let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + let popupclosed = BrowserTestUtils.waitForEvent(gURLBar.popup, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(button, {}); + yield loaded; + yield popupclosed; + + is(gBrowser.selectedBrowser.currentURI.spec, "about:preferences#search", + "Should have loaded the right page"); + }); +}); diff --git a/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js new file mode 100644 index 000000000..6b6a10ea3 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js @@ -0,0 +1,138 @@ +"use strict"; + +const SLOW_PAGE = "http://www.example.com/browser/browser/base/content/test/urlbar/slow-page.sjs"; +const SLOW_PAGE2 = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/slow-page.sjs?faster"; + +/** + * Check that if we: + * 1) have a loaded page + * 2) load a separate URL + * 3) before the URL for step 2 has finished loading, load a third URL + * we don't revert to the URL from (1). + */ +add_task(function*() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com", true, true); + + let expectedURLBarChange = SLOW_PAGE; + let sawChange = false; + let handler = e => { + sawChange = true; + is(gURLBar.value, expectedURLBarChange, "Should not change URL bar value!"); + }; + + let obs = new MutationObserver(handler); + + obs.observe(gURLBar, {attributes: true}); + gURLBar.value = SLOW_PAGE; + gURLBar.handleCommand(); + + // If this ever starts going intermittent, we've broken this. + yield new Promise(resolve => setTimeout(resolve, 200)); + expectedURLBarChange = SLOW_PAGE2; + let pageLoadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + gURLBar.value = expectedURLBarChange; + gURLBar.handleCommand(); + is(gURLBar.value, expectedURLBarChange, "Should not have changed URL bar value synchronously."); + yield pageLoadPromise; + ok(sawChange, "The URL bar change handler should have been called by the time the page was loaded"); + obs.disconnect(); + obs = null; + yield BrowserTestUtils.removeTab(tab); +}); + +/** + * Check that if we: + * 1) middle-click a link to a separate page whose server doesn't respond + * 2) we switch to that tab and stop the request + * + * The URL bar continues to contain the URL of the page we wanted to visit. + */ +add_task(function*() { + let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket); + socket.init(-1, true, -1); + const PORT = socket.port; + registerCleanupFunction(() => { socket.close(); }); + + const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com"); + const BASE_PAGE = TEST_PATH + "dummy_page.html"; + const SLOW_HOST = `https://localhost:${PORT}/`; + info("Using URLs: " + SLOW_HOST); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE); + info("opened tab"); + yield ContentTask.spawn(tab.linkedBrowser, SLOW_HOST, URL => { + let link = content.document.createElement("a"); + link.href = URL; + link.textContent = "click me to open a slow page"; + link.id = "clickme" + content.document.body.appendChild(link); + }); + info("added link"); + let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + // Middle click the link: + yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser); + // get new tab, switch to it + let newTab = (yield newTabPromise).target; + yield BrowserTestUtils.switchTab(gBrowser, newTab); + is(gURLBar.value, SLOW_HOST, "Should have slow page in URL bar"); + let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser); + BrowserStop(); + yield browserStoppedPromise; + + is(gURLBar.value, SLOW_HOST, "Should still have slow page in URL bar after stop"); + yield BrowserTestUtils.removeTab(newTab); + yield BrowserTestUtils.removeTab(tab); +}); +/** + * Check that if we: + * 1) middle-click a link to a separate page whose server doesn't respond + * 2) we alter the URL on that page to some other server that doesn't respond + * 3) we stop the request + * + * The URL bar continues to contain the second URL. + */ +add_task(function*() { + let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket); + socket.init(-1, true, -1); + const PORT1 = socket.port; + let socket2 = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket); + socket2.init(-1, true, -1); + const PORT2 = socket2.port; + registerCleanupFunction(() => { socket.close(); socket2.close(); }); + + const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com"); + const BASE_PAGE = TEST_PATH + "dummy_page.html"; + const SLOW_HOST1 = `https://localhost:${PORT1}/`; + const SLOW_HOST2 = `https://localhost:${PORT2}/`; + info("Using URLs: " + SLOW_HOST1 + " and " + SLOW_HOST2); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE); + info("opened tab"); + yield ContentTask.spawn(tab.linkedBrowser, SLOW_HOST1, URL => { + let link = content.document.createElement("a"); + link.href = URL; + link.textContent = "click me to open a slow page"; + link.id = "clickme" + content.document.body.appendChild(link); + }); + info("added link"); + let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + // Middle click the link: + yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser); + // get new tab, switch to it + let newTab = (yield newTabPromise).target; + yield BrowserTestUtils.switchTab(gBrowser, newTab); + is(gURLBar.value, SLOW_HOST1, "Should have slow page in URL bar"); + let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser); + gURLBar.value = SLOW_HOST2; + gURLBar.handleCommand(); + yield browserStoppedPromise; + + is(gURLBar.value, SLOW_HOST2, "Should have second slow page in URL bar"); + browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser); + BrowserStop(); + yield browserStoppedPromise; + + is(gURLBar.value, SLOW_HOST2, "Should still have second slow page in URL bar after stop"); + yield BrowserTestUtils.removeTab(newTab); + yield BrowserTestUtils.removeTab(tab); +}); + diff --git a/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js new file mode 100644 index 000000000..54b174aa8 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function testURLBarCopy(targetValue) { + return new Promise((resolve, reject) => { + info("Expecting copy of: " + targetValue); + waitForClipboard(targetValue, function () { + gURLBar.focus(); + gURLBar.select(); + + goDoCommand("cmd_copy"); + }, resolve, () => { + ok(false, "Clipboard copy failed"); + reject(); + }); + }); +} + +add_task(function* () { + const url = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/test_wyciwyg_copying.html"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#btn", {}, tab.linkedBrowser); + let currentURL = gBrowser.currentURI.spec; + ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI"); + + yield testURLBarCopy(url); + + while (gBrowser.tabs.length > 1) + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/urlbar/dummy_page.html b/browser/base/content/test/urlbar/dummy_page.html new file mode 100644 index 000000000..1a87e2840 --- /dev/null +++ b/browser/base/content/test/urlbar/dummy_page.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Dummy test page</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>Dummy test page</p> +</body> +</html> diff --git a/browser/base/content/test/urlbar/file_blank_but_not_blank.html b/browser/base/content/test/urlbar/file_blank_but_not_blank.html new file mode 100644 index 000000000..1f5fea8dc --- /dev/null +++ b/browser/base/content/test/urlbar/file_blank_but_not_blank.html @@ -0,0 +1,2 @@ +<script>var q = "1";</script> +<a href="javascript:q">Click me</a> diff --git a/browser/base/content/test/urlbar/file_urlbar_edit_dos.html b/browser/base/content/test/urlbar/file_urlbar_edit_dos.html new file mode 100644 index 000000000..5a6e7d109 --- /dev/null +++ b/browser/base/content/test/urlbar/file_urlbar_edit_dos.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> +<title>Try editing the URL bar</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<script> +var dos_timeout = null; +function dos_hash() { + dos_timeout = setTimeout(function() { + location.hash = "#"; + }, 50); +} + +function dos_pushState() { + dos_timeout = setTimeout(function() { + history.pushState({}, "Some title", ""); + }, 50); +} +</script> +</body> +</html> diff --git a/browser/base/content/test/urlbar/head.js b/browser/base/content/test/urlbar/head.js new file mode 100644 index 000000000..427dba080 --- /dev/null +++ b/browser/base/content/test/urlbar/head.js @@ -0,0 +1,205 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Preferences", + "resource://gre/modules/Preferences.jsm"); + +/** + * Waits for the next top-level document load in the current browser. The URI + * of the document is compared against aExpectedURL. The load is then stopped + * before it actually starts. + * + * @param aExpectedURL + * The URL of the document that is expected to load. + * @param aStopFromProgressListener + * Whether to cancel the load directly from the progress listener. Defaults to true. + * If you're using this method to avoid hitting the network, you want the default (true). + * However, the browser UI will behave differently for loads stopped directly from + * the progress listener (effectively in the middle of a call to loadURI) and so there + * are cases where you may want to avoid stopping the load directly from within the + * progress listener callback. + * @return promise + */ +function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) { + function content_script(aStopFromProgressListener) { + let { interfaces: Ci, utils: Cu } = Components; + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + let wp = docShell.QueryInterface(Ci.nsIWebProgress); + + function stopContent(now, uri) { + if (now) { + /* Hammer time. */ + content.stop(); + + /* Let the parent know we're done. */ + sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri }); + } else { + setTimeout(stopContent.bind(null, true, uri), 0); + } + } + + let progressListener = { + onStateChange: function (webProgress, req, flags, status) { + dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n"); + + if (webProgress.isTopLevel && + flags & Ci.nsIWebProgressListener.STATE_START) { + wp.removeProgressListener(progressListener); + + let chan = req.QueryInterface(Ci.nsIChannel); + dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`); + + stopContent(aStopFromProgressListener, chan.originalURI.spec); + } + }, + QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"]) + }; + wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW); + + /** + * As |this| is undefined and we can't extend |docShell|, adding an unload + * event handler is the easiest way to ensure the weakly referenced + * progress listener is kept alive as long as necessary. + */ + addEventListener("unload", function () { + try { + wp.removeProgressListener(progressListener); + } catch (e) { /* Will most likely fail. */ } + }); + } + + return new Promise((resolve, reject) => { + function complete({ data }) { + is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded"); + mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete); + resolve(); + } + + let mm = aBrowser.messageManager; + mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true); + mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete); + info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL); + }); +} + +function is_hidden(element) { + var style = element.ownerGlobal.getComputedStyle(element); + if (style.display == "none") + return true; + if (style.visibility != "visible") + return true; + if (style.display == "-moz-popup") + return ["hiding", "closed"].indexOf(element.state) != -1; + + // Hiding a parent element will hide all its children + if (element.parentNode != element.ownerDocument) + return is_hidden(element.parentNode); + + return false; +} + +function is_visible(element) { + var style = element.ownerGlobal.getComputedStyle(element); + if (style.display == "none") + return false; + if (style.visibility != "visible") + return false; + if (style.display == "-moz-popup" && element.state != "open") + return false; + + // Hiding a parent element will hide all its children + if (element.parentNode != element.ownerDocument) + return is_visible(element.parentNode); + + return true; +} + +function is_element_visible(element, msg) { + isnot(element, null, "Element should not be null, when checking visibility"); + ok(is_visible(element), msg || "Element should be visible"); +} + +function is_element_hidden(element, msg) { + isnot(element, null, "Element should not be null, when checking visibility"); + ok(is_hidden(element), msg || "Element should be hidden"); +} + +function promisePopupEvent(popup, eventSuffix) { + let endState = {shown: "open", hidden: "closed"}[eventSuffix]; + + if (popup.state == endState) + return Promise.resolve(); + + let eventType = "popup" + eventSuffix; + let deferred = Promise.defer(); + popup.addEventListener(eventType, function onPopupShown(event) { + popup.removeEventListener(eventType, onPopupShown); + deferred.resolve(); + }); + + return deferred.promise; +} + +function promisePopupShown(popup) { + return promisePopupEvent(popup, "shown"); +} + +function promisePopupHidden(popup) { + return promisePopupEvent(popup, "hidden"); +} + +function promiseSearchComplete(win = window) { + return promisePopupShown(win.gURLBar.popup).then(() => { + function searchIsComplete() { + return win.gURLBar.controller.searchStatus >= + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH; + } + + // Wait until there are at least two matches. + return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete"); + }); +} + +function promiseAutocompleteResultPopup(inputText, + win = window, + fireInputEvent = false) { + waitForFocus(() => { + win.gURLBar.focus(); + win.gURLBar.value = inputText; + if (fireInputEvent) { + // This is necessary to get the urlbar to set gBrowser.userTypedValue. + let event = document.createEvent("Events"); + event.initEvent("input", true, true); + win.gURLBar.dispatchEvent(event); + } + win.gURLBar.controller.startSearch(inputText); + }, win); + + return promiseSearchComplete(win); +} + +function promiseNewSearchEngine(basename) { + return new Promise((resolve, reject) => { + info("Waiting for engine to be added: " + basename); + let url = getRootDirectory(gTestPath) + basename; + Services.search.addEngine(url, null, "", false, { + onSuccess: function (engine) { + info("Search engine added: " + basename); + registerCleanupFunction(() => Services.search.removeEngine(engine)); + resolve(engine); + }, + onError: function (errCode) { + Assert.ok(false, "addEngine failed with error code " + errCode); + reject(); + }, + }); + }); +} + diff --git a/browser/base/content/test/urlbar/moz.png b/browser/base/content/test/urlbar/moz.png Binary files differnew file mode 100644 index 000000000..769c63634 --- /dev/null +++ b/browser/base/content/test/urlbar/moz.png diff --git a/browser/base/content/test/urlbar/print_postdata.sjs b/browser/base/content/test/urlbar/print_postdata.sjs new file mode 100644 index 000000000..4175a2480 --- /dev/null +++ b/browser/base/content/test/urlbar/print_postdata.sjs @@ -0,0 +1,22 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + if (request.method == "GET") { + response.write(request.queryString); + } else { + var body = new BinaryInputStream(request.bodyInputStream); + + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + + var data = String.fromCharCode.apply(null, bytes); + response.bodyOutputStream.write(data, data.length); + } +} diff --git a/browser/base/content/test/urlbar/redirect_bug623155.sjs b/browser/base/content/test/urlbar/redirect_bug623155.sjs new file mode 100644 index 000000000..64c6f143b --- /dev/null +++ b/browser/base/content/test/urlbar/redirect_bug623155.sjs @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host. + +function handleRequest(aRequest, aResponse) { + // Set HTTP Status + aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently"); + + // Set redirect URI, mirroring the hash value. + let hash = (/\#.+/.test(aRequest.path))? + "#" + aRequest.path.split("#")[1]: + ""; + aResponse.setHeader("Location", REDIRECT_TO + hash); +} diff --git a/browser/base/content/test/urlbar/searchSuggestionEngine.sjs b/browser/base/content/test/urlbar/searchSuggestionEngine.sjs new file mode 100644 index 000000000..1978b4f66 --- /dev/null +++ b/browser/base/content/test/urlbar/searchSuggestionEngine.sjs @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function handleRequest(req, resp) { + let suffixes = ["foo", "bar"]; + let data = [req.queryString, suffixes.map(s => req.queryString + s)]; + resp.setHeader("Content-Type", "application/json", false); + resp.write(JSON.stringify(data)); +} diff --git a/browser/base/content/test/urlbar/searchSuggestionEngine.xml b/browser/base/content/test/urlbar/searchSuggestionEngine.xml new file mode 100644 index 000000000..a5659792e --- /dev/null +++ b/browser/base/content/test/urlbar/searchSuggestionEngine.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> + +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName> +<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/urlbar/searchSuggestionEngine.sjs?{searchTerms}"/> +<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/> +</SearchPlugin> diff --git a/browser/base/content/test/urlbar/slow-page.sjs b/browser/base/content/test/urlbar/slow-page.sjs new file mode 100644 index 000000000..f428d66e4 --- /dev/null +++ b/browser/base/content/test/urlbar/slow-page.sjs @@ -0,0 +1,22 @@ +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +let timer; + +const DELAY_MS = 5000; +function handleRequest(request, response) { + if (request.queryString.endsWith("faster")) { + response.setHeader("Content-Type", "text/html", false); + response.write("<body>Not so slow!</body>"); + return; + } + response.processAsync(); + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(() => { + response.setHeader("Content-Type", "text/html", false); + response.write("<body>This was the slow load. You should never see this.</body>"); + response.finish(); + }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT); +} diff --git a/browser/base/content/test/urlbar/test_wyciwyg_copying.html b/browser/base/content/test/urlbar/test_wyciwyg_copying.html new file mode 100644 index 000000000..3a8c3a150 --- /dev/null +++ b/browser/base/content/test/urlbar/test_wyciwyg_copying.html @@ -0,0 +1,13 @@ +<html> +<body> +<script> + function go() { + var w = window.open(); + w.document.open(); + w.document.write("<html><body>test document</body></html>"); + w.document.close(); + } +</script> +<button id="btn" onclick="go();">test</button> +</body> +</html> diff --git a/browser/base/content/test/webrtc/.eslintrc.js b/browser/base/content/test/webrtc/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/webrtc/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/webrtc/browser.ini b/browser/base/content/test/webrtc/browser.ini new file mode 100644 index 000000000..8830989ad --- /dev/null +++ b/browser/base/content/test/webrtc/browser.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + get_user_media.html + get_user_media_content_script.js + head.js + +[browser_devices_get_user_media.js] +skip-if = (os == "linux" && debug) # linux: bug 976544 +[browser_devices_get_user_media_anim.js] +[browser_devices_get_user_media_in_frame.js] +[browser_devices_get_user_media_tear_off_tab.js] diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media.js b/browser/base/content/test/webrtc/browser_devices_get_user_media.js new file mode 100644 index 000000000..3681a810b --- /dev/null +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js @@ -0,0 +1,554 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +requestLongerTimeout(2); + +registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); +}); + +const permissionError = "error: NotAllowedError: The request is not allowed " + + "by the user agent or the platform in the current context."; + +var gTests = [ + +{ + desc: "getUserMedia audio+video", + run: function* checkAudioVideo() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareDevices-notification-icon", "anchored to device icon"); + checkDeviceSelectors(true, true); + let iconclass = + PopupNotifications.panel.firstChild.getAttribute("iconclass"); + ok(iconclass.includes("camera-icon"), "panel using devices icon"); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({audio: true, video: true}); + yield closeStream(); + } +}, + +{ + desc: "getUserMedia audio only", + run: function* checkAudioOnly() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareMicrophone-notification-icon", "anchored to mic icon"); + checkDeviceSelectors(true); + let iconclass = + PopupNotifications.panel.firstChild.getAttribute("iconclass"); + ok(iconclass.includes("microphone-icon"), "panel using microphone icon"); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "Microphone", + "expected microphone to be shared"); + + yield indicator; + yield checkSharingUI({audio: true}); + yield closeStream(); + } +}, + +{ + desc: "getUserMedia video only", + run: function* checkVideoOnly() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(false, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareDevices-notification-icon", "anchored to device icon"); + checkDeviceSelectors(false, true); + let iconclass = + PopupNotifications.panel.firstChild.getAttribute("iconclass"); + ok(iconclass.includes("camera-icon"), "panel using devices icon"); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "Camera", "expected camera to be shared"); + + yield indicator; + yield checkSharingUI({video: true}); + yield closeStream(); + } +}, + +{ + desc: "getUserMedia audio+video, user clicks \"Don't Share\"", + run: function* checkDontShare() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + yield promiseMessage(permissionError, () => { + activateSecondaryAction(kActionDeny); + }); + + yield expectObserverCalled("getUserMedia:response:deny"); + yield expectObserverCalled("recording-window-ended"); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia audio+video: stop sharing", + run: function* checkStopSharing() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: true, audio: true}); + + yield stopSharing(); + + // the stream is already closed, but this will do some cleanup anyway + yield closeStream(true); + } +}, + +{ + desc: "getUserMedia audio+video: reloading the page removes all gUM UI", + run: function* checkReloading() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: true, audio: true}); + + info("reloading the web page"); + promise = promiseObserverCalled("recording-device-events"); + content.location.reload(); + yield promise; + + yield expectObserverCalled("recording-window-ended"); + yield expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia prompt: Always/Never Share", + run: function* checkRememberCheckbox() { + let elt = id => document.getElementById(id); + + function* checkPerm(aRequestAudio, aRequestVideo, + aExpectedAudioPerm, aExpectedVideoPerm, aNever) { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(aRequestAudio, aRequestVideo); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + is(elt("webRTC-selectMicrophone").hidden, !aRequestAudio, + "microphone selector expected to be " + (aRequestAudio ? "visible" : "hidden")); + + is(elt("webRTC-selectCamera").hidden, !aRequestVideo, + "camera selector expected to be " + (aRequestVideo ? "visible" : "hidden")); + + let expectedMessage = aNever ? permissionError : "ok"; + yield promiseMessage(expectedMessage, () => { + activateSecondaryAction(aNever ? kActionNever : kActionAlways); + }); + let expected = []; + if (expectedMessage == "ok") { + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + if (aRequestVideo) + expected.push("Camera"); + if (aRequestAudio) + expected.push("Microphone"); + expected = expected.join("And"); + } + else { + yield expectObserverCalled("getUserMedia:response:deny"); + yield expectObserverCalled("recording-window-ended"); + expected = "none"; + } + is((yield getMediaCaptureState()), expected, + "expected " + expected + " to be shared"); + + function checkDevicePermissions(aDevice, aExpected) { + let Perms = Services.perms; + let uri = gBrowser.selectedBrowser.documentURI; + let devicePerms = Perms.testExactPermission(uri, aDevice); + if (aExpected === undefined) + is(devicePerms, Perms.UNKNOWN_ACTION, "no " + aDevice + " persistent permissions"); + else { + is(devicePerms, aExpected ? Perms.ALLOW_ACTION : Perms.DENY_ACTION, + aDevice + " persistently " + (aExpected ? "allowed" : "denied")); + } + Perms.remove(uri, aDevice); + } + checkDevicePermissions("microphone", aExpectedAudioPerm); + checkDevicePermissions("camera", aExpectedVideoPerm); + + if (expectedMessage == "ok") + yield closeStream(); + } + + // 3 cases where the user accepts the device prompt. + info("audio+video, user grants, expect both perms set to allow"); + yield checkPerm(true, true, true, true); + info("audio only, user grants, check audio perm set to allow, video perm not set"); + yield checkPerm(true, false, true, undefined); + info("video only, user grants, check video perm set to allow, audio perm not set"); + yield checkPerm(false, true, undefined, true); + + // 3 cases where the user rejects the device request by using 'Never Share'. + info("audio only, user denies, expect audio perm set to deny, video not set"); + yield checkPerm(true, false, false, undefined, true); + info("video only, user denies, expect video perm set to deny, audio perm not set"); + yield checkPerm(false, true, undefined, false, true); + info("audio+video, user denies, expect both perms set to deny"); + yield checkPerm(true, true, false, false, true); + } +}, + +{ + desc: "getUserMedia without prompt: use persistent permissions", + run: function* checkUsePersistentPermissions() { + function* usePerm(aAllowAudio, aAllowVideo, aRequestAudio, aRequestVideo, + aExpectStream) { + let Perms = Services.perms; + let uri = gBrowser.selectedBrowser.documentURI; + + if (aAllowAudio !== undefined) { + Perms.add(uri, "microphone", aAllowAudio ? Perms.ALLOW_ACTION + : Perms.DENY_ACTION); + } + if (aAllowVideo !== undefined) { + Perms.add(uri, "camera", aAllowVideo ? Perms.ALLOW_ACTION + : Perms.DENY_ACTION); + } + + if (aExpectStream === undefined) { + // Check that we get a prompt. + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(aRequestAudio, aRequestVideo); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + // Deny the request to cleanup... + yield promiseMessage(permissionError, () => { + activateSecondaryAction(kActionDeny); + }); + yield expectObserverCalled("getUserMedia:response:deny"); + yield expectObserverCalled("recording-window-ended"); + } + else { + let expectedMessage = aExpectStream ? "ok" : permissionError; + let promise = promiseMessage(expectedMessage); + yield promiseRequestDevice(aRequestAudio, aRequestVideo); + yield promise; + + if (expectedMessage == "ok") { + yield expectObserverCalled("getUserMedia:request"); + yield promiseNoPopupNotification("webRTC-shareDevices"); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + + // Check what's actually shared. + let expected = []; + if (aAllowVideo && aRequestVideo) + expected.push("Camera"); + if (aAllowAudio && aRequestAudio) + expected.push("Microphone"); + expected = expected.join("And"); + is((yield getMediaCaptureState()), expected, + "expected " + expected + " to be shared"); + + yield closeStream(); + } + else { + yield expectObserverCalled("recording-window-ended"); + } + } + + Perms.remove(uri, "camera"); + Perms.remove(uri, "microphone"); + } + + // Set both permissions identically + info("allow audio+video, request audio+video, expect ok (audio+video)"); + yield usePerm(true, true, true, true, true); + info("deny audio+video, request audio+video, expect denied"); + yield usePerm(false, false, true, true, false); + + // Allow audio, deny video. + info("allow audio, deny video, request audio+video, expect denied"); + yield usePerm(true, false, true, true, false); + info("allow audio, deny video, request audio, expect ok (audio)"); + yield usePerm(true, false, true, false, true); + info("allow audio, deny video, request video, expect denied"); + yield usePerm(true, false, false, true, false); + + // Deny audio, allow video. + info("deny audio, allow video, request audio+video, expect denied"); + yield usePerm(false, true, true, true, false); + info("deny audio, allow video, request audio, expect denied"); + yield usePerm(false, true, true, false, false); + info("deny audio, allow video, request video, expect ok (video)"); + yield usePerm(false, true, false, true, true); + + // Allow audio, video not set. + info("allow audio, request audio+video, expect prompt"); + yield usePerm(true, undefined, true, true, undefined); + info("allow audio, request audio, expect ok (audio)"); + yield usePerm(true, undefined, true, false, true); + info("allow audio, request video, expect prompt"); + yield usePerm(true, undefined, false, true, undefined); + + // Deny audio, video not set. + info("deny audio, request audio+video, expect denied"); + yield usePerm(false, undefined, true, true, false); + info("deny audio, request audio, expect denied"); + yield usePerm(false, undefined, true, false, false); + info("deny audio, request video, expect prompt"); + yield usePerm(false, undefined, false, true, undefined); + + // Allow video, audio not set. + info("allow video, request audio+video, expect prompt"); + yield usePerm(undefined, true, true, true, undefined); + info("allow video, request audio, expect prompt"); + yield usePerm(undefined, true, true, false, undefined); + info("allow video, request video, expect ok (video)"); + yield usePerm(undefined, true, false, true, true); + + // Deny video, audio not set. + info("deny video, request audio+video, expect denied"); + yield usePerm(undefined, false, true, true, false); + info("deny video, request audio, expect prompt"); + yield usePerm(undefined, false, true, false, undefined); + info("deny video, request video, expect denied"); + yield usePerm(undefined, false, false, true, false); + } +}, + +{ + desc: "Stop Sharing removes persistent permissions", + run: function* checkStopSharingRemovesPersistentPermissions() { + function* stopAndCheckPerm(aRequestAudio, aRequestVideo) { + let Perms = Services.perms; + let uri = gBrowser.selectedBrowser.documentURI; + + // Initially set both permissions to 'allow'. + Perms.add(uri, "microphone", Perms.ALLOW_ACTION); + Perms.add(uri, "camera", Perms.ALLOW_ACTION); + + let indicator = promiseIndicatorWindow(); + // Start sharing what's been requested. + let promise = promiseMessage("ok"); + yield promiseRequestDevice(aRequestAudio, aRequestVideo); + yield promise; + + yield expectObserverCalled("getUserMedia:request"); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + yield indicator; + yield checkSharingUI({video: aRequestVideo, audio: aRequestAudio}); + + yield stopSharing(aRequestVideo ? "camera" : "microphone"); + + // Check that permissions have been removed as expected. + let audioPerm = Perms.testExactPermission(uri, "microphone"); + if (aRequestAudio) + is(audioPerm, Perms.UNKNOWN_ACTION, "microphone permissions removed"); + else + is(audioPerm, Perms.ALLOW_ACTION, "microphone permissions untouched"); + + let videoPerm = Perms.testExactPermission(uri, "camera"); + if (aRequestVideo) + is(videoPerm, Perms.UNKNOWN_ACTION, "camera permissions removed"); + else + is(videoPerm, Perms.ALLOW_ACTION, "camera permissions untouched"); + + // Cleanup. + yield closeStream(true); + + Perms.remove(uri, "camera"); + Perms.remove(uri, "microphone"); + } + + info("request audio+video, stop sharing resets both"); + yield stopAndCheckPerm(true, true); + info("request audio, stop sharing resets audio only"); + yield stopAndCheckPerm(true, false); + info("request video, stop sharing resets video only"); + yield stopAndCheckPerm(false, true); + } +}, + +{ + desc: "test showControlCenter", + run: function* checkShowControlCenter() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(false, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(false, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "Camera", "expected camera to be shared"); + + yield indicator; + yield checkSharingUI({video: true}); + + ok(gIdentityHandler._identityPopup.hidden, "control center should be hidden"); + if ("nsISystemStatusBar" in Ci) { + let activeStreams = webrtcUI.getActiveStreams(true, false, false); + webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices"); + } + else { + let win = + Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator"); + let elt = win.document.getElementById("audioVideoButton"); + EventUtils.synthesizeMouseAtCenter(elt, {}, win); + yield promiseWaitForCondition(() => !gIdentityHandler._identityPopup.hidden); + } + ok(!gIdentityHandler._identityPopup.hidden, "control center should be open"); + + gIdentityHandler._identityPopup.hidden = true; + yield expectNoObserverCalled(); + + yield closeStream(); + } +}, + +{ + desc: "'Always Allow' ignored and not shown on http pages", + run: function* checkNoAlwaysOnHttp() { + // Load an http page instead of the https version. + let browser = gBrowser.selectedBrowser; + browser.loadURI(browser.documentURI.spec.replace("https://", "http://")); + yield BrowserTestUtils.browserLoaded(browser); + + // Initially set both permissions to 'allow'. + let Perms = Services.perms; + let uri = browser.documentURI; + Perms.add(uri, "microphone", Perms.ALLOW_ACTION); + Perms.add(uri, "camera", Perms.ALLOW_ACTION); + + // Request devices and expect a prompt despite the saved 'Allow' permission, + // because the connection isn't secure. + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + // Ensure that the 'Always Allow' action isn't shown. + let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label"); + ok(!!alwaysLabel, "found the 'Always Allow' localized label"); + let labels = []; + let notification = PopupNotifications.panel.firstChild; + for (let node of notification.childNodes) { + if (node.localName == "menuitem") + labels.push(node.getAttribute("label")); + } + is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown"); + + // Cleanup. + yield closeStream(true); + Perms.remove(uri, "camera"); + Perms.remove(uri, "microphone"); + } +} + +]; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + + browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true); + + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + + is(PopupNotifications._currentNotifications.length, 0, + "should start the test without any prior popup notification"); + ok(gIdentityHandler._identityPopup.hidden, + "should start the test with the control center hidden"); + + Task.spawn(function* () { + yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]}); + + for (let test of gTests) { + info(test.desc); + yield test.run(); + + // Cleanup before the next test + yield expectNoObserverCalled(); + } + }).then(finish, ex => { + Cu.reportError(ex); + ok(false, "Unexpected Exception: " + ex); + finish(); + }); + }, true); + let rootDir = getRootDirectory(gTestPath); + rootDir = rootDir.replace("chrome://mochitests/content/", + "https://example.com/"); + content.location = rootDir + "get_user_media.html"; +} diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js new file mode 100644 index 000000000..f407061a7 --- /dev/null +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); +}); + +var gTests = [ + +{ + desc: "device sharing animation on background tabs", + run: function* checkAudioVideo() { + function* getStreamAndCheckBackgroundAnim(aAudio, aVideo, aSharing) { + // Get a stream + let popupPromise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(aAudio, aVideo); + yield popupPromise; + yield expectObserverCalled("getUserMedia:request"); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + let expected = []; + if (aVideo) + expected.push("Camera"); + if (aAudio) + expected.push("Microphone"); + is((yield getMediaCaptureState()), expected.join("And"), + "expected stream to be shared"); + + // Check the attribute on the tab, and check there's no visible + // sharing icon on the tab + let tab = gBrowser.selectedTab; + is(tab.getAttribute("sharing"), aSharing, + "the tab has the attribute to show the " + aSharing + " icon"); + let icon = + document.getAnonymousElementByAttribute(tab, "anonid", "sharing-icon"); + is(window.getComputedStyle(icon).display, "none", + "the animated sharing icon of the tab is hidden"); + + // After selecting a new tab, check the attribute is still there, + // and the icon is now visible. + yield BrowserTestUtils.switchTab(gBrowser, gBrowser.addTab()); + is(gBrowser.selectedTab.getAttribute("sharing"), "", + "the new tab doesn't have the 'sharing' attribute"); + is(tab.getAttribute("sharing"), aSharing, + "the tab still has the 'sharing' attribute"); + isnot(window.getComputedStyle(icon).display, "none", + "the animated sharing icon of the tab is now visible"); + + // Ensure the icon disappears when selecting the tab. + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + ok(tab.selected, "the tab with ongoing sharing is selected again"); + is(window.getComputedStyle(icon).display, "none", + "the animated sharing icon is gone after selecting the tab again"); + + // And finally verify the attribute is removed when closing the stream. + yield closeStream(); + + // TODO(Bug 1304997): Fix the race in closeStream() and remove this + // promiseWaitForCondition(). + yield promiseWaitForCondition(() => !tab.getAttribute("sharing")); + is(tab.getAttribute("sharing"), "", + "the tab no longer has the 'sharing' attribute after closing the stream"); + } + + yield getStreamAndCheckBackgroundAnim(true, true, "camera"); + yield getStreamAndCheckBackgroundAnim(false, true, "camera"); + yield getStreamAndCheckBackgroundAnim(true, false, "microphone"); + } +} + +]; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + + browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true); + + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + + is(PopupNotifications._currentNotifications.length, 0, + "should start the test without any prior popup notification"); + + Task.spawn(function* () { + yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]}); + + for (let test of gTests) { + info(test.desc); + yield test.run(); + } + }).then(finish, ex => { + Cu.reportError(ex); + ok(false, "Unexpected Exception: " + ex); + finish(); + }); + }, true); + let rootDir = getRootDirectory(gTestPath); + rootDir = rootDir.replace("chrome://mochitests/content/", + "https://example.com/"); + content.location = rootDir + "get_user_media.html"; +} diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js new file mode 100644 index 000000000..01a544aae --- /dev/null +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js @@ -0,0 +1,266 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); +}); + +function promiseReloadFrame(aFrameId) { + return ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) { + content.wrappedJSObject.document.getElementById(aFrameId).contentWindow.location.reload(); + }); +} + +var gTests = [ + +{ + desc: "getUserMedia audio+video", + run: function* checkAudioVideo() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true, "frame1"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareDevices-notification-icon", "anchored to device icon"); + checkDeviceSelectors(true, true); + is(PopupNotifications.panel.firstChild.getAttribute("popupid"), + "webRTC-shareDevices", "panel using devices icon"); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({audio: true, video: true}); + yield closeStream(false, "frame1"); + } +}, + +{ + desc: "getUserMedia audio+video: stop sharing", + run: function* checkStopSharing() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true, "frame1"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + activateSecondaryAction(kActionAlways); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: true, audio: true}); + + let Perms = Services.perms; + let uri = Services.io.newURI("https://example.com/", null, null); + is(Perms.testExactPermission(uri, "microphone"), Perms.ALLOW_ACTION, + "microphone persistently allowed"); + is(Perms.testExactPermission(uri, "camera"), Perms.ALLOW_ACTION, + "camera persistently allowed"); + + yield stopSharing(); + + // The persistent permissions for the frame should have been removed. + is(Perms.testExactPermission(uri, "microphone"), Perms.UNKNOWN_ACTION, + "microphone not persistently allowed"); + is(Perms.testExactPermission(uri, "camera"), Perms.UNKNOWN_ACTION, + "camera not persistently allowed"); + + // the stream is already closed, but this will do some cleanup anyway + yield closeStream(true, "frame1"); + } +}, + +{ + desc: "getUserMedia audio+video: reloading the frame removes all sharing UI", + run: function* checkReloading() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true, "frame1"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: true, audio: true}); + + info("reloading the frame"); + promise = promiseObserverCalled("recording-device-events"); + yield promiseReloadFrame("frame1"); + yield promise; + + yield expectObserverCalled("recording-window-ended"); + yield expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia audio+video: reloading the frame removes prompts", + run: function* checkReloadingRemovesPrompts() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true, "frame1"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + info("reloading the frame"); + promise = promiseObserverCalled("recording-window-ended"); + yield promiseReloadFrame("frame1"); + yield promise; + yield promiseNoPopupNotification("webRTC-shareDevices"); + + yield expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia audio+video: reloading a frame updates the sharing UI", + run: function* checkUpdateWhenReloading() { + // We'll share only the mic in the first frame, then share both in the + // second frame, then reload the second frame. After each step, we'll check + // the UI is in the correct state. + + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, false, "frame1"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, false); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "Microphone", "microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: false, audio: true}); + yield expectNoObserverCalled(); + + promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true, "frame2"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield checkSharingUI({video: true, audio: true}); + yield expectNoObserverCalled(); + + info("reloading the second frame"); + promise = promiseObserverCalled("recording-device-events"); + yield promiseReloadFrame("frame2"); + yield promise; + + yield expectObserverCalled("recording-window-ended"); + yield checkSharingUI({video: false, audio: true}); + yield expectNoObserverCalled(); + + yield closeStream(false, "frame1"); + yield expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia audio+video: reloading the top level page removes all sharing UI", + run: function* checkReloading() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true, "frame1"); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: true, audio: true}); + + info("reloading the web page"); + promise = promiseObserverCalled("recording-device-events"); + content.location.reload(); + yield promise; + + yield expectObserverCalled("recording-window-ended"); + yield expectNoObserverCalled(); + yield checkNotSharing(); + } +} + +]; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + + browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true); + + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + + is(PopupNotifications._currentNotifications.length, 0, + "should start the test without any prior popup notification"); + + Task.spawn(function* () { + yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]}); + + for (let test of gTests) { + info(test.desc); + yield test.run(); + + // Cleanup before the next test + yield expectNoObserverCalled(); + } + }).then(finish, ex => { + Cu.reportError(ex); + ok(false, "Unexpected Exception: " + ex); + finish(); + }); + }, true); + let rootDir = getRootDirectory(gTestPath); + rootDir = rootDir.replace("chrome://mochitests/content/", + "https://example.com/"); + let url = rootDir + "get_user_media.html"; + content.location = 'data:text/html,<iframe id="frame1" src="' + url + '"></iframe><iframe id="frame2" src="' + url + '"></iframe>' +} diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js new file mode 100644 index 000000000..b19065371 --- /dev/null +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); +}); + +var gTests = [ + +{ + desc: "getUserMedia: tearing-off a tab keeps sharing indicators", + run: function* checkTearingOff() { + let promise = promisePopupNotificationShown("webRTC-shareDevices"); + yield promiseRequestDevice(true, true); + yield promise; + yield expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + let indicator = promiseIndicatorWindow(); + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + yield expectObserverCalled("getUserMedia:response:allow"); + yield expectObserverCalled("recording-device-events"); + is((yield getMediaCaptureState()), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield indicator; + yield checkSharingUI({video: true, audio: true}); + + info("tearing off the tab"); + let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + yield whenDelayedStartupFinished(win); + yield checkSharingUI({audio: true, video: true}, win); + + // Clicking the global sharing indicator should open the control center in + // the second window. + ok(win.gIdentityHandler._identityPopup.hidden, "control center should be hidden"); + let activeStreams = webrtcUI.getActiveStreams(true, false, false); + webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices"); + ok(!win.gIdentityHandler._identityPopup.hidden, + "control center should be open in the second window"); + ok(gIdentityHandler._identityPopup.hidden, + "control center should be hidden in the first window"); + win.gIdentityHandler._identityPopup.hidden = true; + + // Closing the new window should remove all sharing indicators. + // We need to load the content script in the first window so that we can + // catch the notifications fired globally when closing the second window. + gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true); + + let promises = [promiseObserverCalled("recording-device-events"), + promiseObserverCalled("recording-window-ended")]; + yield BrowserTestUtils.closeWindow(win); + yield Promise.all(promises); + + yield expectNoObserverCalled(); + yield checkNotSharing(); + } +} + +]; + +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, runTest); +} + +function runTest() { + // An empty tab where we can load the content script without leaving it + // behind at the end of the test. + gBrowser.addTab(); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + + browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true); + + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + + is(PopupNotifications._currentNotifications.length, 0, + "should start the test without any prior popup notification"); + ok(gIdentityHandler._identityPopup.hidden, + "should start the test with the control center hidden"); + + Task.spawn(function* () { + yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]}); + + for (let test of gTests) { + info(test.desc); + yield test.run(); + + // Cleanup before the next test + yield expectNoObserverCalled(); + } + }).then(finish, ex => { + Cu.reportError(ex); + ok(false, "Unexpected Exception: " + ex); + finish(); + }); + }, true); + let rootDir = getRootDirectory(gTestPath); + rootDir = rootDir.replace("chrome://mochitests/content/", + "https://example.com/"); + content.location = rootDir + "get_user_media.html"; +} diff --git a/browser/base/content/test/webrtc/get_user_media.html b/browser/base/content/test/webrtc/get_user_media.html new file mode 100644 index 000000000..16303c62d --- /dev/null +++ b/browser/base/content/test/webrtc/get_user_media.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> +<head><meta charset="UTF-8"></head> +<body> +<div id="message"></div> +<script> +// Specifies whether we are using fake streams to run this automation +var useFakeStreams = true; +try { + var audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev"); + var videoDevice = SpecialPowers.getCharPref("media.video_loopback_dev"); + dump("TEST DEVICES: Using media devices:\n"); + dump("audio: " + audioDevice + "\nvideo: " + videoDevice + "\n"); + useFakeStreams = false; +} catch (e) { + dump("TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n"); + useFakeStreams = true; +} + +function message(m) { + document.getElementById("message").innerHTML = m; + window.parent.postMessage(m, "*"); +} + +var gStream; + +function requestDevice(aAudio, aVideo, aShare) { + var opts = {video: aVideo, audio: aAudio}; + if (aShare) { + opts.video = { + mozMediaSource: aShare, + mediaSource: aShare + } + } else if (useFakeStreams) { + opts.fake = true; + } + + window.navigator.mediaDevices.getUserMedia(opts) + .then(stream => { + gStream = stream; + message("ok"); + }, err => message("error: " + err)); +} +message("pending"); + +function closeStream() { + if (!gStream) + return; + gStream.getTracks().forEach(t => t.stop()); + gStream = null; + message("closed"); +} +</script> +</body> +</html> diff --git a/browser/base/content/test/webrtc/get_user_media_content_script.js b/browser/base/content/test/webrtc/get_user_media_content_script.js new file mode 100644 index 000000000..71b68d826 --- /dev/null +++ b/browser/base/content/test/webrtc/get_user_media_content_script.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", + "@mozilla.org/mediaManagerService;1", + "nsIMediaManagerService"); + +const kObservedTopics = [ + "getUserMedia:response:allow", + "getUserMedia:revoke", + "getUserMedia:response:deny", + "getUserMedia:request", + "recording-device-events", + "recording-window-ended" +]; + +var gObservedTopics = {}; +function observer(aSubject, aTopic, aData) { + if (!(aTopic in gObservedTopics)) + gObservedTopics[aTopic] = 1; + else + ++gObservedTopics[aTopic]; +} + +kObservedTopics.forEach(topic => { + Services.obs.addObserver(observer, topic, false); +}); + +addMessageListener("Test:ExpectObserverCalled", ({data}) => { + sendAsyncMessage("Test:ExpectObserverCalled:Reply", + {count: gObservedTopics[data]}); + if (data in gObservedTopics) + --gObservedTopics[data]; +}); + +addMessageListener("Test:ExpectNoObserverCalled", data => { + sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics); + gObservedTopics = {}; +}); + +function _getMediaCaptureState() { + let hasVideo = {}; + let hasAudio = {}; + let hasScreenShare = {}; + let hasWindowShare = {}; + MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio, + hasScreenShare, hasWindowShare); + if (hasVideo.value && hasAudio.value) + return "CameraAndMicrophone"; + if (hasVideo.value) + return "Camera"; + if (hasAudio.value) + return "Microphone"; + if (hasScreenShare.value) + return "Screen"; + if (hasWindowShare.value) + return "Window"; + return "none"; +} + +addMessageListener("Test:GetMediaCaptureState", data => { + sendAsyncMessage("Test:MediaCaptureState", _getMediaCaptureState()); +}); + +addMessageListener("Test:WaitForObserverCall", ({data}) => { + let topic = data; + Services.obs.addObserver(function observer() { + sendAsyncMessage("Test:ObserverCalled", topic); + Services.obs.removeObserver(observer, topic); + + if (kObservedTopics.indexOf(topic) != -1) { + if (!(topic in gObservedTopics)) + gObservedTopics[topic] = -1; + else + --gObservedTopics[topic]; + } + }, topic, false); +}); + +addMessageListener("Test:WaitForMessage", () => { + content.addEventListener("message", ({data}) => { + sendAsyncMessage("Test:MessageReceived", data); + }, {once: true}); +}); diff --git a/browser/base/content/test/webrtc/head.js b/browser/base/content/test/webrtc/head.js new file mode 100644 index 000000000..70b183773 --- /dev/null +++ b/browser/base/content/test/webrtc/head.js @@ -0,0 +1,453 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); + +const PREF_PERMISSION_FAKE = "media.navigator.permission.fake"; +const CONTENT_SCRIPT_HELPER = getRootDirectory(gTestPath) + "get_user_media_content_script.js"; + +function waitForCondition(condition, nextTest, errorMsg, retryTimes) { + retryTimes = typeof retryTimes !== 'undefined' ? retryTimes : 30; + var tries = 0; + var interval = setInterval(function() { + if (tries >= retryTimes) { + ok(false, errorMsg); + moveOn(); + } + var conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function() { clearInterval(interval); nextTest(); }; +} + +function promiseWaitForCondition(aConditionFn) { + let deferred = Promise.defer(); + waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass."); + return deferred.promise; +} + +/** + * Waits for a window with the given URL to exist. + * + * @param url + * The url of the window. + * @return {Promise} resolved when the window exists. + * @resolves to the window + */ +function promiseWindow(url) { + info("expecting a " + url + " window"); + return new Promise(resolve => { + Services.obs.addObserver(function obs(win) { + win.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function loadHandler() { + win.removeEventListener("load", loadHandler); + + if (win.location.href !== url) { + info("ignoring a window with this url: " + win.location.href); + return; + } + + Services.obs.removeObserver(obs, "domwindowopened"); + resolve(win); + }); + }, "domwindowopened", false); + }); +} + +function whenDelayedStartupFinished(aWindow) { + return new Promise(resolve => { + info("Waiting for delayed startup to finish"); + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + resolve(); + } + }, "browser-delayed-startup-finished", false); + }); +} + +function promiseIndicatorWindow() { + // We don't show the indicator window on Mac. + if ("nsISystemStatusBar" in Ci) + return Promise.resolve(); + + return promiseWindow("chrome://browser/content/webrtcIndicator.xul"); +} + +function* assertWebRTCIndicatorStatus(expected) { + let ui = Cu.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI; + let expectedState = expected ? "visible" : "hidden"; + let msg = "WebRTC indicator " + expectedState; + if (!expected && ui.showGlobalIndicator) { + // It seems the global indicator is not always removed synchronously + // in some cases. + info("waiting for the global indicator to be hidden"); + yield promiseWaitForCondition(() => !ui.showGlobalIndicator); + } + is(ui.showGlobalIndicator, !!expected, msg); + + let expectVideo = false, expectAudio = false, expectScreen = false; + if (expected) { + if (expected.video) + expectVideo = true; + if (expected.audio) + expectAudio = true; + if (expected.screen) + expectScreen = true; + } + is(ui.showCameraIndicator, expectVideo, "camera global indicator as expected"); + is(ui.showMicrophoneIndicator, expectAudio, "microphone global indicator as expected"); + is(ui.showScreenSharingIndicator, expectScreen, "screen global indicator as expected"); + + let windows = Services.wm.getEnumerator("navigator:browser"); + while (windows.hasMoreElements()) { + let win = windows.getNext(); + let menu = win.document.getElementById("tabSharingMenu"); + is(menu && !menu.hidden, !!expected, "WebRTC menu should be " + expectedState); + } + + if (!("nsISystemStatusBar" in Ci)) { + if (!expected) { + let win = Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator"); + if (win) { + yield new Promise((resolve, reject) => { + win.addEventListener("unload", function listener(e) { + if (e.target == win.document) { + win.removeEventListener("unload", listener); + resolve(); + } + }, false); + }); + } + } + let indicator = Services.wm.getEnumerator("Browser:WebRTCGlobalIndicator"); + let hasWindow = indicator.hasMoreElements(); + is(hasWindow, !!expected, "popup " + msg); + if (hasWindow) { + let document = indicator.getNext().document; + let docElt = document.documentElement; + + if (document.readyState != "complete") { + info("Waiting for the sharing indicator's document to load"); + let deferred = Promise.defer(); + document.addEventListener("readystatechange", + function onReadyStateChange() { + if (document.readyState != "complete") + return; + document.removeEventListener("readystatechange", onReadyStateChange); + deferred.resolve(); + }); + yield deferred.promise; + } + + for (let item of ["video", "audio", "screen"]) { + let expectedValue = (expected && expected[item]) ? "true" : ""; + is(docElt.getAttribute("sharing" + item), expectedValue, + item + " global indicator attribute as expected"); + } + + ok(!indicator.hasMoreElements(), "only one global indicator window"); + } + } +} + +function promisePopupEvent(popup, eventSuffix) { + let endState = {shown: "open", hidden: "closed"}[eventSuffix]; + + if (popup.state == endState) + return Promise.resolve(); + + let eventType = "popup" + eventSuffix; + let deferred = Promise.defer(); + popup.addEventListener(eventType, function onPopupShown(event) { + popup.removeEventListener(eventType, onPopupShown); + deferred.resolve(); + }); + + return deferred.promise; +} + +function promiseNotificationShown(notification) { + let win = notification.browser.ownerGlobal; + if (win.PopupNotifications.panel.state == "open") { + return Promise.resolve(); + } + let panelPromise = promisePopupEvent(win.PopupNotifications.panel, "shown"); + notification.reshow(); + return panelPromise; +} + +function _mm() { + return gBrowser.selectedBrowser.messageManager; +} + +function promiseObserverCalled(aTopic) { + return new Promise(resolve => { + let mm = _mm(); + mm.addMessageListener("Test:ObserverCalled", function listener({data}) { + if (data == aTopic) { + ok(true, "got " + aTopic + " notification"); + mm.removeMessageListener("Test:ObserverCalled", listener); + resolve(); + } + }); + mm.sendAsyncMessage("Test:WaitForObserverCall", aTopic); + }); +} + +function expectObserverCalled(aTopic) { + return new Promise(resolve => { + let mm = _mm(); + mm.addMessageListener("Test:ExpectObserverCalled:Reply", + function listener({data}) { + is(data.count, 1, "expected notification " + aTopic); + mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener); + resolve(); + }); + mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic); + }); +} + +function expectNoObserverCalled() { + return new Promise(resolve => { + let mm = _mm(); + mm.addMessageListener("Test:ExpectNoObserverCalled:Reply", + function listener({data}) { + mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener); + for (let topic in data) { + if (data[topic]) + is(data[topic], 0, topic + " notification unexpected"); + } + resolve(); + }); + mm.sendAsyncMessage("Test:ExpectNoObserverCalled"); + }); +} + +function promiseMessage(aMessage, aAction) { + let promise = new Promise((resolve, reject) => { + let mm = _mm(); + mm.addMessageListener("Test:MessageReceived", function listener({data}) { + is(data, aMessage, "received " + aMessage); + if (data == aMessage) + resolve(); + else + reject(); + mm.removeMessageListener("Test:MessageReceived", listener); + }); + mm.sendAsyncMessage("Test:WaitForMessage"); + }); + + if (aAction) + aAction(); + + return promise; +} + +function promisePopupNotificationShown(aName, aAction) { + let deferred = Promise.defer(); + + PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() { + PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown); + + ok(!!PopupNotifications.getNotification(aName), aName + " notification shown"); + ok(PopupNotifications.isPanelOpen, "notification panel open"); + ok(!!PopupNotifications.panel.firstChild, "notification panel populated"); + + deferred.resolve(); + }); + + if (aAction) + aAction(); + + return deferred.promise; +} + +function promisePopupNotification(aName) { + let deferred = Promise.defer(); + + waitForCondition(() => PopupNotifications.getNotification(aName), + () => { + ok(!!PopupNotifications.getNotification(aName), + aName + " notification appeared"); + + deferred.resolve(); + }, "timeout waiting for popup notification " + aName); + + return deferred.promise; +} + +function promiseNoPopupNotification(aName) { + let deferred = Promise.defer(); + + waitForCondition(() => !PopupNotifications.getNotification(aName), + () => { + ok(!PopupNotifications.getNotification(aName), + aName + " notification removed"); + deferred.resolve(); + }, "timeout waiting for popup notification " + aName + " to disappear"); + + return deferred.promise; +} + +const kActionAlways = 1; +const kActionDeny = 2; +const kActionNever = 3; + +function activateSecondaryAction(aAction) { + let notification = PopupNotifications.panel.firstChild; + notification.button.focus(); + let popup = notification.menupopup; + popup.addEventListener("popupshown", function () { + popup.removeEventListener("popupshown", arguments.callee, false); + + // Press 'down' as many time as needed to select the requested action. + while (aAction--) + EventUtils.synthesizeKey("VK_DOWN", {}); + + // Activate + EventUtils.synthesizeKey("VK_RETURN", {}); + }, false); + + // One down event to open the popup + EventUtils.synthesizeKey("VK_DOWN", + { altKey: !navigator.platform.includes("Mac") }); +} + +function getMediaCaptureState() { + return new Promise(resolve => { + let mm = _mm(); + mm.addMessageListener("Test:MediaCaptureState", ({data}) => { + resolve(data); + }); + mm.sendAsyncMessage("Test:GetMediaCaptureState"); + }); +} + +function* stopSharing(aType = "camera") { + let promiseRecordingEvent = promiseObserverCalled("recording-device-events"); + gIdentityHandler._identityBox.click(); + let permissions = document.getElementById("identity-popup-permission-list"); + let cancelButton = + permissions.querySelector(".identity-popup-permission-icon." + aType + "-icon ~ " + + ".identity-popup-permission-remove-button"); + cancelButton.click(); + gIdentityHandler._identityPopup.hidden = true; + yield promiseRecordingEvent; + yield expectObserverCalled("getUserMedia:revoke"); + yield expectObserverCalled("recording-window-ended"); + yield expectNoObserverCalled(); + yield* checkNotSharing(); +} + +function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType) { + info("requesting devices"); + return ContentTask.spawn(gBrowser.selectedBrowser, + {aRequestAudio, aRequestVideo, aFrameId, aType}, + function*(args) { + let global = content.wrappedJSObject; + if (args.aFrameId) + global = global.document.getElementById(args.aFrameId).contentWindow; + global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType); + }); +} + +function* closeStream(aAlreadyClosed, aFrameId) { + yield expectNoObserverCalled(); + + let promises; + if (!aAlreadyClosed) { + promises = [promiseObserverCalled("recording-device-events"), + promiseObserverCalled("recording-window-ended")]; + } + + info("closing the stream"); + yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) { + let global = content.wrappedJSObject; + if (aFrameId) + global = global.document.getElementById(aFrameId).contentWindow; + global.closeStream(); + }); + + if (promises) + yield Promise.all(promises); + + yield* assertWebRTCIndicatorStatus(null); +} + +function checkDeviceSelectors(aAudio, aVideo) { + let micSelector = document.getElementById("webRTC-selectMicrophone"); + if (aAudio) + ok(!micSelector.hidden, "microphone selector visible"); + else + ok(micSelector.hidden, "microphone selector hidden"); + + let cameraSelector = document.getElementById("webRTC-selectCamera"); + if (aVideo) + ok(!cameraSelector.hidden, "camera selector visible"); + else + ok(cameraSelector.hidden, "camera selector hidden"); +} + +function* checkSharingUI(aExpected, aWin = window) { + let doc = aWin.document; + // First check the icon above the control center (i) icon. + let identityBox = doc.getElementById("identity-box"); + ok(identityBox.hasAttribute("sharing"), "sharing attribute is set"); + let sharing = identityBox.getAttribute("sharing"); + if (aExpected.video) + is(sharing, "camera", "showing camera icon on the control center icon"); + else if (aExpected.audio) + is(sharing, "microphone", "showing mic icon on the control center icon"); + + // Then check the sharing indicators inside the control center panel. + identityBox.click(); + let permissions = doc.getElementById("identity-popup-permission-list"); + for (let id of ["microphone", "camera", "screen"]) { + let convertId = id => { + if (id == "camera") + return "video"; + if (id == "microphone") + return "audio"; + return id; + }; + let expected = aExpected[convertId(id)]; + is(!!aWin.gIdentityHandler._sharingState[id], !!expected, + "sharing state for " + id + " as expected"); + let icon = permissions.querySelectorAll( + ".identity-popup-permission-icon." + id + "-icon"); + if (expected) { + is(icon.length, 1, "should show " + id + " icon in control center panel"); + ok(icon[0].classList.contains("in-use"), "icon should have the in-use class"); + } else if (!icon.length) { + ok(true, "should not show " + id + " icon in the control center panel"); + } else { + // This will happen if there are persistent permissions set. + ok(!icon[0].classList.contains("in-use"), + "if shown, the " + id + " icon should not have the in-use class"); + is(icon.length, 1, "should not show more than 1 " + id + " icon"); + } + } + aWin.gIdentityHandler._identityPopup.hidden = true; + + // Check the global indicators. + yield* assertWebRTCIndicatorStatus(aExpected); +} + +function* checkNotSharing() { + is((yield getMediaCaptureState()), "none", "expected nothing to be shared"); + + ok(!document.getElementById("identity-box").hasAttribute("sharing"), + "no sharing indicator on the control center icon"); + + yield* assertWebRTCIndicatorStatus(null); +} |