diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/base/content/test/general | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
171 files changed, 16124 insertions, 0 deletions
diff --git a/browser/base/content/test/general/alltabslistener.html b/browser/base/content/test/general/alltabslistener.html new file mode 100644 index 0000000000..166c31037a --- /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 0000000000..13c525487e --- /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 0000000000..8690497ffb --- /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 0000000000..477544875d --- /dev/null +++ b/browser/base/content/test/general/audio.ogg diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini new file mode 100644 index 0000000000..2e9438135a --- /dev/null +++ b/browser/base/content/test/general/browser.ini @@ -0,0 +1,416 @@ +############################################################################### +# DO NOT ADD MORE TESTS HERE. # +# TRY ONE OF THE MORE TOPICAL SIBLING DIRECTORIES. # +# THIS DIRECTORY HAS 200+ TESTS AND TAKES AGES TO RUN ON A DEBUG BUILD. # +# PLEASE, FOR THE LOVE OF WHATEVER YOU HOLD DEAR, DO NOT ADD MORE TESTS HERE. # +############################################################################### + +[DEFAULT] +support-files = + alltabslistener.html + app_bug575561.html + app_subframe_bug575561.html + audio.ogg + browser_bug479408_sample.html + browser_star_hsts.sjs + browser_tab_dragdrop2_frame1.xhtml + browser_tab_dragdrop_embed.html + bug792517-2.html + bug792517.html + bug792517.sjs + clipboard_pastefile.html + download_page.html + download_page_1.txt + download_page_2.txt + download_with_content_disposition_header.sjs + dummy_page.html + file_documentnavigation_frameset.html + file_double_close_tab.html + file_fullscreen-window-open.html + file_with_link_to_http.html + head.js + moz.png + navigating_window_with_download.html + print_postdata.sjs + test_bug462673.html + test_bug628179.html + title_test.svg + unknownContentType_file.pif + unknownContentType_file.pif^headers^ + video.ogg + web_video.html + web_video1.ogv + web_video1.ogv^headers^ + !/image/test/mochitest/blue.png + !/toolkit/content/tests/browser/common/mockTransfer.js + +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_accesskeys.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_addCertException.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_alltabslistener.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_backButtonFitts.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_beforeunload_duplicate_dialogs.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug1261299.js] +skip-if = + os != "mac" # Because of tests for supporting Service Menu of macOS, bug 1261299 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug1297539.js] +skip-if = + os != "mac" # Because of tests for supporting pasting from Service Menu of macOS, bug 1297539 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug1299667.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug321000.js] +skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528) +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug356571.js] +skip-if = + verify && !debug && os == 'win' +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug380960.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug406216.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug417483.js] +skip-if = + verify && debug && os == 'mac' + os == 'mac' + os == 'linux' #Bug 1444703 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug424101.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug427559.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug431826.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug432599.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug455852.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug462289.js] +skip-if = + os == "mac" +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug462673.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug477014.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug479408.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug481560.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug484315.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug491431.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug495058.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug519216.js] +skip-if = true # Bug 1478159 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug520538.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug521216.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug533232.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug537013.js] +skip-if = true # bug 1393813 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug537474.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug563588.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug565575.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug567306.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug575561.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug577121.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug578534.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug579872.js] +skip-if = + verify && debug && os == 'linux' + os == 'mac' + os == 'linux' && !debug #Bug 1448915 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug581253.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug585785.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug585830.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug594131.js] +skip-if = + verify && debug && os == 'linux' +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug596687.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug597218.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug609700.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug623893.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug624734.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug664672.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug676619.js] +support-files = + dummy.ics + dummy.ics^headers^ + redirect_download.sjs +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug710878.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug724239.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug734076.js] +skip-if = + verify && debug && os == 'linux' +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug749738.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug763468_perwindowpb.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug767836_perwindowpb.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug817947.js] +skip-if = + os == 'linux' && !debug # Bug 1556066 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug832435.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug882977.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_bug963945.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_clipboard.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_clipboard_pastefile.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_contentAltClick.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_contentAreaClick.js] +skip-if = true # Clicks in content don't go through contentAreaClick. +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_ctrlTab.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_datachoices_notification.js] +skip-if = + !datareporting + verify && !debug && os == 'win' +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_documentnavigation.js] +skip-if = + verify && !debug && os == 'linux' +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_domFullscreen_fullscreenMode.js] +tags = fullscreen +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_double_close_tab.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_drag.js] +skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638. +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_duplicateIDs.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_findbarClose.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_focusonkeydown.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_fullscreen-window-open.js] +tags = fullscreen +skip-if = + os == "linux" # Linux: Intermittent failures - bug 941575. +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_gestureSupport.js] +support-files = + !/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js + !/gfx/layers/apz/test/mochitest/apz_test_utils.js +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_hide_removing.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_homeDrop.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_invalid_uri_back_forward_manipulation.js] +skip-if = + os == 'mac' && socketprocess_networking +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_lastAccessedTab.js] +skip-if = + os == "windows" # Disabled on Windows due to frequent failures (bug 969405) +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_menuButtonFitts.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_middleMouse_noJSPaste.js] +https_first_disabled = true +skip-if = + apple_silicon && !debug # Bug 1724711 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_minimize.js] +skip-if = + apple_silicon && !debug # Bug 1725756 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_modifiedclick_inherit_principal.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_newTabDrop.js] +https_first_disabled = true +skip-if = + os == "linux" && fission && tsan # high frequency intermittent +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_newWindowDrop.js] +https_first_disabled = true +skip-if = + os == "win" && os_version == "6.1" # bug 1715862 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_new_http_window_opened_from_file_tab.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_newwindow_focus.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_plainTextLinks.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_printpreview.js] +skip-if = + os == 'win' + os == 'linux' && os_version == '18.04' # Bug 1384127 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_private_browsing_window.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_private_no_prompt.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_refreshBlocker.js] +skip-if = + os == "mac" + os == "linux" && !debug + os == "win" && bits == 32 # Bug 1559410 for all instances +support-files = + refresh_header.sjs + refresh_meta.sjs +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_relatedTabs.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_remoteTroubleshoot.js] +https_first_disabled = true +skip-if = + !updater + os == 'linux' && asan # Bug 1711507 +reason = depends on UpdateUtils .Locale +support-files = + test_remoteTroubleshoot.html +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_remoteWebNavigation_postdata.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_restore_isAppTab.js] +skip-if = + !crashreporter # test requires crashreporter due to 1536221 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_save_link-perwindowpb.js] +skip-if = + debug && os == "win" + verify # Bug 1280505 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_save_link_when_window_navigates.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_save_private_link_perwindowpb.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_save_video.js] +skip-if = + os == 'mac' + verify && os == 'mac' + os == 'win' && debug + os =='linux' #Bug 1212419 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_save_video_frame.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_selectTabAtIndex.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_star_hsts.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_storagePressure_notification.js] +skip-if = verify +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tabDrop.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tab_close_dependent_window.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tab_detach_restore.js] +https_first_disabled = true +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tab_drag_drop_perwindow.js] +skip-if = + os == "win" && os_version == "6.1" && bits == 32 # bug 1717587 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tab_dragdrop.js] +skip-if = true # Bug 1312436, Bug 1388973 +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tab_dragdrop2.js] +skip-if = + os == "win" && bits == 32 && !debug # high frequency win7 intermittent: crash +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tabfocus.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tabkeynavigation.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tabs_close_beforeunload.js] +support-files = + close_beforeunload_opens_second_tab.html + close_beforeunload.html +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tabs_isActive.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_tabs_owner.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_typeAheadFind.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_unknownContentType_title.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_unloaddialogs.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_viewSourceInTabOnViewSource.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_visibleFindSelection.js] +skip-if = true # Bug 1409184 disabled because interactive find next is not automating properly +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_visibleTabs.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_visibleTabs_bookmarkAllPages.js] +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_visibleTabs_tabPreview.js] +skip-if = + os == "win" && !debug +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_windowactivation.js] +skip-if = + verify + os == "linux" && debug # Bug 1678774 +support-files = + file_window_activation.html + file_window_activation2.html +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. +[browser_zbug569342.js] +skip-if = true # Bug 1094240 - has findbar-related failures +# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. 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 0000000000..c8b27d6307 --- /dev/null +++ b/browser/base/content/test/general/browser_accesskeys.js @@ -0,0 +1,202 @@ +add_task(async function () { + await 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 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1); + + Services.focus.clearFocus(window); + + // Press an accesskey in the child document while the chrome is focused. + let focusedId = await performAccessKey(tab1.linkedBrowser, "y"); + is(focusedId, "button", "button accesskey"); + + // Press an accesskey in the child document while the content document is focused. + focusedId = await performAccessKey(tab1.linkedBrowser, "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.createXULElement("button"); + newButton.id = "chromebutton"; + newButton.setAttribute("accesskey", "z"); + document.documentElement.appendChild(newButton); + + Services.focus.clearFocus(window); + + newButton.getBoundingClientRect(); // Accesskey registration happens during frame construction. + + focusedId = await 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 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2); + + Services.focus.clearFocus(window); + + focusedId = await performAccessKey(tab2.linkedBrowser, "y"); + is(focusedId, "tab2button", "button accesskey in tab2"); + + // Press the accesskey for the chrome element while the content document is focused. + focusedId = await performAccessKeyForChrome("z"); + is(focusedId, "chromebutton", "chromebutton accesskey"); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); + + // Test whether access key for the newButton isn't available when content + // consumes the key event. + + // When content in the tab3 consumes all keydown events. + const gPageURL3 = + "data:text/html,<body id='tab3body'>" + + "<button id='tab3button' accesskey='y'>Button in Tab 3</button>" + + "<script>" + + "document.body.addEventListener('keydown', (event)=>{ event.preventDefault(); });" + + "</script></body>"; + let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3); + + Services.focus.clearFocus(window); + + focusedId = await performAccessKey(tab3.linkedBrowser, "y"); + is(focusedId, "tab3button", "button accesskey in tab3 should be focused"); + + newButton.onfocus = () => { + ok(false, "chromebutton shouldn't get focus during testing with tab3"); + }; + + // Press the accesskey for the chrome element while the content document is focused. + focusedId = await performAccessKey(tab3.linkedBrowser, "z"); + is( + focusedId, + "tab3body", + "button accesskey in tab3 should keep having focus" + ); + + newButton.onfocus = null; + + gBrowser.removeTab(tab3); + + // When content in the tab4 consumes all keypress events. + const gPageURL4 = + "data:text/html,<body id='tab4body'>" + + "<button id='tab4button' accesskey='y'>Button in Tab 4</button>" + + "<script>" + + "document.body.addEventListener('keypress', (event)=>{ event.preventDefault(); });" + + "</script></body>"; + let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4); + + Services.focus.clearFocus(window); + + focusedId = await performAccessKey(tab4.linkedBrowser, "y"); + is(focusedId, "tab4button", "button accesskey in tab4 should be focused"); + + newButton.onfocus = () => { + // EventStateManager handles accesskey before dispatching keypress event + // into the DOM tree, therefore, chrome accesskey always wins focus from + // content. However, this is different from shortcut keys. + todo(false, "chromebutton shouldn't get focus during testing with tab4"); + }; + + // Press the accesskey for the chrome element while the content document is focused. + focusedId = await performAccessKey(tab4.linkedBrowser, "z"); + is( + focusedId, + "tab4body", + "button accesskey in tab4 should keep having focus" + ); + + newButton.onfocus = null; + + gBrowser.removeTab(tab4); + + newButton.remove(); +}); + +function performAccessKey(browser, key) { + return new Promise(resolve => { + let removeFocus, removeKeyDown, removeKeyUp; + function callback(eventName, result) { + removeFocus(); + removeKeyUp(); + removeKeyDown(); + + SpecialPowers.spawn(browser, [], () => { + let oldFocusedElement = content._oldFocusedElement; + delete content._oldFocusedElement; + return oldFocusedElement.id; + }).then(oldFocus => resolve(oldFocus)); + } + + removeFocus = BrowserTestUtils.addContentEventListener( + browser, + "focus", + callback, + { capture: true }, + event => { + if (!HTMLElement.isInstance(event.target)) { + return false; // ignore window and document focus events + } + + event.target.ownerGlobal._sent = true; + let focusedElement = event.target.ownerGlobal.document.activeElement; + event.target.ownerGlobal._oldFocusedElement = focusedElement; + focusedElement.blur(); + return true; + } + ); + + removeKeyDown = BrowserTestUtils.addContentEventListener( + browser, + "keydown", + () => {}, + { capture: true }, + event => { + event.target.ownerGlobal._sent = false; + return true; + } + ); + + removeKeyUp = BrowserTestUtils.addContentEventListener( + browser, + "keyup", + callback, + {}, + event => { + if (!event.target.ownerGlobal._sent) { + event.target.ownerGlobal._sent = true; + let focusedElement = event.target.ownerGlobal.document.activeElement; + event.target.ownerGlobal._oldFocusedElement = focusedElement; + focusedElement.blur(); + return true; + } + + return false; + } + ); + + // Spawn an no-op content task to better ensure that the messages + // for adding the event listeners above get handled. + SpecialPowers.spawn(browser, [], () => {}).then(() => { + EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true }); + }); + }); +} + +// This version is used when a chrome element is expected to be found for an accesskey. +async function performAccessKeyForChrome(key, inChild) { + let waitFocusChangePromise = BrowserTestUtils.waitForEvent( + document, + "focus", + true + ); + EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true }); + await 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 0000000000..d3d1ac1ce4 --- /dev/null +++ b/browser/base/content/test/general/browser_addCertException.js @@ -0,0 +1,77 @@ +/* -*- 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(async function () { + await BrowserTestUtils.openNewForegroundTab(gBrowser); + await loadBadCertPage("https://expired.example.com"); + + let { gIdentityHandler } = gBrowser.ownerGlobal; + let promisePanelOpen = BrowserTestUtils.waitForEvent( + gBrowser.ownerGlobal, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + + let promiseViewShown = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "ViewShown" + ); + document.getElementById("identity-popup-security-button").click(); + await promiseViewShown; + + is_element_visible( + document.getElementById("identity-icon"), + "Should see identity icon" + ); + let identityIconImage = gBrowser.ownerGlobal + .getComputedStyle(document.getElementById("identity-icon")) + .getPropertyValue("list-style-image"); + let securityViewBG = gBrowser.ownerGlobal + .getComputedStyle( + document + .getElementById("identity-popup-securityView") + .getElementsByClassName("identity-popup-security-connection")[0] + ) + .getPropertyValue("list-style-image"); + let securityContentBG = gBrowser.ownerGlobal + .getComputedStyle( + document + .getElementById("identity-popup-mainView") + .getElementsByClassName("identity-popup-security-connection")[0] + ) + .getPropertyValue("list-style-image"); + is( + identityIconImage, + 'url("chrome://global/skin/icons/security-warning.svg")', + "Using expected icon image in the identity block" + ); + is( + securityViewBG, + 'url("chrome://global/skin/icons/security-warning.svg")', + "Using expected icon image in the Control Center main view" + ); + is( + securityContentBG, + 'url("chrome://global/skin/icons/security-warning.svg")', + "Using expected icon image in the Control Center subview" + ); + + gIdentityHandler._identityPopup.hidePopup(); + + let certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" + ].getService(Ci.nsICertOverrideService); + certOverrideService.clearValidityOverride("expired.example.com", -1, {}); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); 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 0000000000..0c9677306d --- /dev/null +++ b/browser/base/content/test/general/browser_alltabslistener.js @@ -0,0 +1,331 @@ +const gCompleteState = + Ci.nsIWebProgressListener.STATE_STOP + + Ci.nsIWebProgressListener.STATE_IS_NETWORK; + +function getOriginalURL(request) { + return request && request.QueryInterface(Ci.nsIChannel).originalURI.spec; +} + +var gFrontProgressListener = { + onProgressChange( + aWebProgress, + aRequest, + aCurSelfProgress, + aMaxSelfProgress, + aCurTotalProgress, + aMaxTotalProgress + ) {}, + + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + var url = getOriginalURL(aRequest); + if (url == "about:blank") { + return; + } + var state = "onStateChange"; + info( + "FrontProgress (" + url + "): " + state + " 0x" + aStateFlags.toString(16) + ); + assertCorrectBrowserAndEventOrderForFront(state); + }, + + onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) { + var url = getOriginalURL(aRequest); + if (url == "about:blank") { + return; + } + var state = "onLocationChange"; + info("FrontProgress: " + state + " " + aLocationURI.spec); + assertCorrectBrowserAndEventOrderForFront(state); + }, + + onSecurityChange(aWebProgress, aRequest, aState) { + var url = getOriginalURL(aRequest); + if (url == "about:blank") { + return; + } + var state = "onSecurityChange"; + info("FrontProgress (" + url + "): " + state + " 0x" + aState.toString(16)); + assertCorrectBrowserAndEventOrderForFront(state); + }, +}; + +function assertCorrectBrowserAndEventOrderForFront(aEventName) { + Assert.less( + gFrontNotificationsPos, + gFrontNotifications.length, + "Got an expected notification for the front notifications listener" + ); + is( + aEventName, + gFrontNotifications[gFrontNotificationsPos], + "Got a notification for the front notifications listener" + ); + gFrontNotificationsPos++; +} + +var gAllProgressListener = { + onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + var url = getOriginalURL(aRequest); + if (url == "about:blank") { + // ignore initial about blank + return; + } + var state = "onStateChange"; + info( + "AllProgress (" + url + "): " + state + " 0x" + aStateFlags.toString(16) + ); + assertCorrectBrowserAndEventOrderForAll(state, aBrowser); + assertReceivedFlags( + state, + gAllNotifications[gAllNotificationsPos], + aStateFlags + ); + gAllNotificationsPos++; + + if ((aStateFlags & gCompleteState) == gCompleteState) { + is( + gAllNotificationsPos, + gAllNotifications.length, + "Saw the expected number of notifications" + ); + is( + gFrontNotificationsPos, + gFrontNotifications.length, + "Saw the expected number of frontnotifications" + ); + executeSoon(gNextTest); + } + }, + + onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) { + var url = getOriginalURL(aRequest); + if (url == "about:blank") { + // ignore initial about blank + return; + } + var state = "onLocationChange"; + info("AllProgress: " + state + " " + aLocationURI.spec); + assertCorrectBrowserAndEventOrderForAll(state, aBrowser); + assertReceivedFlags( + "onLocationChange", + gAllNotifications[gAllNotificationsPos], + aFlags + ); + gAllNotificationsPos++; + }, + + onSecurityChange(aBrowser, aWebProgress, aRequest, aState) { + var url = getOriginalURL(aRequest); + if (url == "about:blank") { + // ignore initial about blank + return; + } + var state = "onSecurityChange"; + info("AllProgress (" + url + "): " + state + " 0x" + aState.toString(16)); + assertCorrectBrowserAndEventOrderForAll(state, aBrowser); + is( + state, + gAllNotifications[gAllNotificationsPos], + "Got a notification for the all notifications listener" + ); + gAllNotificationsPos++; + }, +}; + +function assertCorrectBrowserAndEventOrderForAll(aState, aBrowser) { + ok( + aBrowser == gTestBrowser, + aState + " notification came from the correct browser" + ); + Assert.less( + gAllNotificationsPos, + gAllNotifications.length, + "Got an expected notification for the all notifications listener" + ); +} + +function assertReceivedFlags(aState, aObjOrEvent, aFlags) { + if (aObjOrEvent !== null && typeof aObjOrEvent === "object") { + is( + aState, + aObjOrEvent.state, + "Got a notification for the all notifications listener" + ); + is(aFlags, aFlags & aObjOrEvent.flags, `Got correct flags for ${aState}`); + } else { + is( + aState, + aObjOrEvent, + "Got a notification for the all notifications listener" + ); + } +} + +var gFrontNotifications, + gAllNotifications, + gFrontNotificationsPos, + gAllNotificationsPos; +var gBackgroundTab, + gForegroundTab, + gBackgroundBrowser, + gForegroundBrowser, + gTestBrowser; +var gTestPage = + "/browser/browser/base/content/test/general/alltabslistener.html"; +const kBasePage = + "http://mochi.test:8888/browser/browser/base/content/test/general/dummy_page.html"; +var gNextTest; + +async function test() { + waitForExplicitFinish(); + + gBackgroundTab = BrowserTestUtils.addTab(gBrowser); + gForegroundTab = BrowserTestUtils.addTab(gBrowser); + gBackgroundBrowser = gBrowser.getBrowserForTab(gBackgroundTab); + gForegroundBrowser = gBrowser.getBrowserForTab(gForegroundTab); + gBrowser.selectedTab = gForegroundTab; + + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onStateChange", + ]; + + // We must wait until a page has completed loading before + // starting tests or we get notifications from that + let promises = [ + BrowserTestUtils.browserStopped(gBackgroundBrowser, kBasePage), + BrowserTestUtils.browserStopped(gForegroundBrowser, kBasePage), + ]; + BrowserTestUtils.loadURIString(gBackgroundBrowser, kBasePage); + BrowserTestUtils.loadURIString(gForegroundBrowser, kBasePage); + await Promise.all(promises); + // If we process switched, the tabbrowser may still be processing the state_stop + // notification here because of how microtasks work. Ensure that that has + // happened before starting to test (which would add listeners to the tabbrowser + // which would get confused by being called about kBasePage loading). + await new Promise(executeSoon); + startTest1(); +} + +function runTest(browser, url, next) { + gFrontNotificationsPos = 0; + gAllNotificationsPos = 0; + gNextTest = next; + gTestBrowser = browser; + BrowserTestUtils.loadURIString(browser, url); +} + +function startTest1() { + info("\nTest 1"); + gBrowser.addProgressListener(gFrontProgressListener); + gBrowser.addTabsProgressListener(gAllProgressListener); + + gFrontNotifications = gAllNotifications; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2); +} + +function startTest2() { + info("\nTest 2"); + gFrontNotifications = gAllNotifications; + runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3); +} + +function startTest3() { + info("\nTest 3"); + gFrontNotifications = []; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4); +} + +function startTest4() { + info("\nTest 4"); + 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); + + gFrontNotifications = gAllNotifications; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6); +} + +function startTest6() { + info("\nTest 6"); + gFrontNotifications = []; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest7); +} + +// Navigate from remote to non-remote +function startTest7() { + info("\nTest 7"); + gFrontNotifications = []; + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + { + state: "onLocationChange", + flags: Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT, + }, // dummy onLocationChange event + "onStateChange", + ]; + runTest(gBackgroundBrowser, "about:preferences", startTest8); +} + +// Navigate from non-remote to non-remote +function startTest8() { + info("\nTest 8"); + gFrontNotifications = []; + gAllNotifications = [ + "onStateChange", + { + state: "onStateChange", + flags: + Ci.nsIWebProgressListener.STATE_IS_REDIRECTED_DOCUMENT | + Ci.nsIWebProgressListener.STATE_IS_REQUEST | + Ci.nsIWebProgressListener.STATE_START, + }, + "onLocationChange", + "onSecurityChange", + "onStateChange", + ]; + runTest(gBackgroundBrowser, "about:config", startTest9); +} + +// Navigate from non-remote to remote +function startTest9() { + info("\nTest 9"); + gFrontNotifications = []; + gAllNotifications = [ + "onStateChange", + "onLocationChange", + "onSecurityChange", + "onStateChange", + ]; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + 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_backButtonFitts.js b/browser/base/content/test/general/browser_backButtonFitts.js new file mode 100644 index 0000000000..8ef3006a2c --- /dev/null +++ b/browser/base/content/test/general/browser_backButtonFitts.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function () { + let firstLocation = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + await BrowserTestUtils.openNewForegroundTab(gBrowser, firstLocation); + + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function () { + // Push the state before maximizing the window and clicking below. + content.history.pushState("page2", "page2", "page2"); + }); + + 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 popStatePromise = BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "popstate", + true + ); + EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window); + await popStatePromise; + + is( + gBrowser.selectedBrowser.currentURI.spec, + 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 0000000000..8a77f01ce4 --- /dev/null +++ b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js @@ -0,0 +1,114 @@ +const TEST_PAGE = + "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html"; + +const CONTENT_PROMPT_SUBDIALOG = Services.prefs.getBoolPref( + "prompts.contentPromptSubDialog", + false +); + +var expectingDialog = false; +var wantToClose = true; +var resolveDialogPromise; + +function onTabModalDialogLoaded(node) { + ok( + !CONTENT_PROMPT_SUBDIALOG, + "Should not be using content prompt subdialogs." + ); + ok(expectingDialog, "Should be expecting this dialog."); + expectingDialog = false; + if (wantToClose) { + // This accepts the dialog, closing it + node.querySelector(".tabmodalprompt-button0").click(); + } else { + // This keeps the page open + node.querySelector(".tabmodalprompt-button1").click(); + } + if (resolveDialogPromise) { + resolveDialogPromise(); + } +} + +function onCommonDialogLoaded(promptWindow) { + ok(CONTENT_PROMPT_SUBDIALOG, "Should be using content prompt subdialogs."); + ok(expectingDialog, "Should be expecting this dialog."); + expectingDialog = false; + let dialog = promptWindow.Dialog; + if (wantToClose) { + // This accepts the dialog, closing it. + dialog.ui.button0.click(); + } else { + // This keeps the page open + 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"); +Services.obs.addObserver(onCommonDialogLoaded, "common-dialog-loaded"); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.tabs.warnOnClose"); + Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded"); + Services.obs.removeObserver(onCommonDialogLoaded, "common-dialog-loaded"); +}); + +add_task(async function closeLastTabInWindow() { + let newWin = await promiseOpenAndLoadWindow({}, true); + let firstTab = newWin.gBrowser.selectedTab; + await promiseTabLoadEvent(firstTab, TEST_PAGE); + let windowClosedPromise = BrowserTestUtils.domWindowClosed(newWin); + expectingDialog = true; + // close tab: + firstTab.closeButton.click(); + await windowClosedPromise; + ok(!expectingDialog, "There should have been a dialog."); + ok(newWin.closed, "Window should be closed."); +}); + +add_task(async function closeWindowWithMultipleTabsIncludingOneBeforeUnload() { + Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); + let newWin = await promiseOpenAndLoadWindow({}, true); + let firstTab = newWin.gBrowser.selectedTab; + await promiseTabLoadEvent(firstTab, TEST_PAGE); + await promiseTabLoadEvent( + BrowserTestUtils.addTab(newWin.gBrowser), + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/" + ); + let windowClosedPromise = BrowserTestUtils.domWindowClosed(newWin); + expectingDialog = true; + newWin.BrowserTryToCloseWindow(); + await windowClosedPromise; + ok(!expectingDialog, "There should have been a dialog."); + ok(newWin.closed, "Window should be closed."); + Services.prefs.clearUserPref("browser.tabs.warnOnClose"); +}); + +add_task(async function closeWindoWithSingleTabTwice() { + let newWin = await promiseOpenAndLoadWindow({}, true); + let firstTab = newWin.gBrowser.selectedTab; + await promiseTabLoadEvent(firstTab, TEST_PAGE); + let windowClosedPromise = BrowserTestUtils.domWindowClosed(newWin); + expectingDialog = true; + wantToClose = false; + let firstDialogShownPromise = new Promise((resolve, reject) => { + resolveDialogPromise = resolve; + }); + firstTab.closeButton.click(); + await firstDialogShownPromise; + info("Got initial dialog, now trying again"); + expectingDialog = true; + wantToClose = true; + resolveDialogPromise = null; + firstTab.closeButton.click(); + await 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_bug1261299.js b/browser/base/content/test/general/browser_bug1261299.js new file mode 100644 index 0000000000..47b82a5da0 --- /dev/null +++ b/browser/base/content/test/general/browser_bug1261299.js @@ -0,0 +1,112 @@ +/* -*- 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(async 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 = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage); + await BrowserTestUtils.synthesizeMouse( + "#textarea", + 0, + 0, + {}, + gBrowser.selectedBrowser + ); + await BrowserTestUtils.synthesizeKey( + "KEY_ArrowRight", + { shiftKey: true, ctrlKey: true }, + gBrowser.selectedBrowser + ); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is( + selectedText, + "Write something here", + "The macOS services got the selected content text" + ); + gURLBar.value = "test.mozilla.org"; + await gURLBar.editor.selectAll(); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is( + selectedText, + "test.mozilla.org", + "The macOS services got the selected chrome text" + ); + + 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(async function test_active_selection_switches_properly() { + let testPage1 = + // eslint-disable-next-line no-useless-concat + "data:text/html," + + '<textarea id="textarea">Write something here</textarea>'; + let testPage2 = + // eslint-disable-next-line no-useless-concat + "data:text/html," + '<textarea id="textarea">Nothing available</textarea>'; + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + let selectedText; + + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1); + await BrowserTestUtils.synthesizeMouse( + "#textarea", + 0, + 0, + {}, + gBrowser.selectedBrowser + ); + await BrowserTestUtils.synthesizeKey( + "KEY_ArrowRight", + { shiftKey: true, ctrlKey: true }, + gBrowser.selectedBrowser + ); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2); + await BrowserTestUtils.synthesizeMouse( + "#textarea", + 0, + 0, + {}, + gBrowser.selectedBrowser + ); + await BrowserTestUtils.synthesizeKey( + "KEY_ArrowRight", + { shiftKey: true, ctrlKey: true }, + gBrowser.selectedBrowser + ); + + await BrowserTestUtils.switchTab(gBrowser, tab1); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is( + selectedText, + "Write something here", + "The macOS services got the selected content text" + ); + + await BrowserTestUtils.switchTab(gBrowser, tab2); + selectedText = DOMWindowUtils.GetSelectionAsPlaintext(); + is( + selectedText, + "Nothing available", + "The macOS services got the selected content text" + ); + + BrowserTestUtils.removeTab(tab1); + 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 0000000000..7572d85eaf --- /dev/null +++ b/browser/base/content/test/general/browser_bug1297539.js @@ -0,0 +1,122 @@ +/* -*- 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.docShell.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/plain"); + } + Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); + return trans; +} + +async function cutCurrentSelection(elementQueryString, property, browser) { + // Cut the current selection. + await BrowserTestUtils.synthesizeKey("x", { accelKey: true }, browser); + + // The editor should be empty after cut. + await SpecialPowers.spawn( + browser, + [[elementQueryString, property]], + async 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(async function test_paste_transferable_plain_text() { + let testPage = + "data:text/html," + + '<textarea id="textarea">Write something here</textarea>'; + + await BrowserTestUtils.withNewTab(testPage, async function (browser) { + // Select all the content in your editor element. + await BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, browser); + await BrowserTestUtils.synthesizeKey("a", { accelKey: true }, browser); + + await cutCurrentSelection("#textarea", "value", browser); + + let trans = getTransferableFromClipboard(false); + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans); + + await SpecialPowers.spawn(browser, [], async 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(async function test_paste_transferable_html() { + let testPage = + "data:text/html," + + '<div contenteditable="true"><b>Bold Text</b><i>italics</i></div>'; + + await BrowserTestUtils.withNewTab(testPage, async function (browser) { + // Select all the content in your editor element. + await BrowserTestUtils.synthesizeMouse("div", 0, 0, {}, browser); + await SpecialPowers.spawn(browser, [], async function () { + let element = content.document.querySelector("div"); + let selection = content.window.getSelection(); + selection.selectAllChildren(element); + }); + + await cutCurrentSelection("div", "textContent", browser); + + let trans = getTransferableFromClipboard(true); + let DOMWindowUtils = EventUtils._getDOMWindowUtils(window); + DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans); + + await SpecialPowers.spawn(browser, [], async 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 0000000000..d281652c44 --- /dev/null +++ b/browser/base/content/test/general/browser_bug1299667.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function () { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com"); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.history.pushState({}, "2", "2.html"); + }); + + await TestUtils.topicObserved("sessionstore-state-write-complete"); + + // Wait for the session data to be flushed before continuing the test + await 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 = await popupShownPromise; + + ok(true, "history menu opened"); + + // Wait for the session data to be flushed before continuing the test + await new Promise(resolve => + SessionStore.getSessionHistory(gBrowser.selectedTab, resolve) + ); + + is(event.target.children.length, 2, "Two history items"); + + let node = event.target.firstElementChild; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + 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.lastElementChild; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + 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(); + await popupHiddenPromise; + info("Hidden popup"); + + let onClose = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabClose" + ); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + await 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 0000000000..78ab74e543 --- /dev/null +++ b/browser/base/content/test/general/browser_bug321000.js @@ -0,0 +1,91 @@ +/* -*- 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(event) { + element.removeEventListener(event.type, this); + + 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); + + // 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 () { + 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 }); + }); + }, + { once: true } + ); + 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 0000000000..185d59d8fd --- /dev/null +++ b/browser/base/content/test/general/browser_bug356571.js @@ -0,0 +1,100 @@ +// Bug 356571 - loadOneOrMoreURIs gives up if one of the URLs has an unknown protocol + +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/prompter;1"; + +// Save original prompt service factory +const kPromptServiceFactory = Cm.getClassObject( + Cc[kPromptServiceContractID], + Ci.nsIFactory +); + +var fakePromptServiceFactory = { + createInstance(aIid) { + return promptService.QueryInterface(aIid); + }, +}; + +var promptService = { + QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]), + alert() { + 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 = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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(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"); + /* 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 0000000000..5571d8f08e --- /dev/null +++ b/browser/base/content/test/general/browser_bug380960.js @@ -0,0 +1,18 @@ +function test() { + var tab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + gBrowser.removeTab(tab); + is(tab.parentNode, null, "tab removed immediately"); + + tab = BrowserTestUtils.addTab(gBrowser, "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_bug406216.js b/browser/base/content/test/general/browser_bug406216.js new file mode 100644 index 0000000000..bee262e4f8 --- /dev/null +++ b/browser/base/content/test/general/browser_bug406216.js @@ -0,0 +1,64 @@ +/* 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 = BrowserTestUtils.addTab(gBrowser, aURI); + if (aIndex == 0) { + gBrowser.removeTab(gBrowser.tabs[0], { skipPermitUnload: true }); + } + + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + if (++count == URIS.length) { + executeSoon(doTabsTest); + } + }); +} + +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) { + var closedTab = event.originalTarget; + var scheme = closedTab.linkedBrowser.currentURI.scheme; + Array.from(gBrowser.tabs).forEach(function (aTab) { + if ( + aTab != closedTab && + aTab.linkedBrowser.currentURI.scheme == scheme + ) { + gBrowser.removeTab(aTab, { skipPermitUnload: true }); + } + }); + }, + { capture: true, once: true } + ); + + gBrowser.removeTab(gBrowser.tabs[0], { skipPermitUnload: true }); + is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly"); + + BrowserTestUtils.addTab(gBrowser, "about:blank"); + gBrowser.removeTab(gBrowser.tabs[0], { skipPermitUnload: true }); + 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 0000000000..6c8619b532 --- /dev/null +++ b/browser/base/content/test/general/browser_bug417483.js @@ -0,0 +1,50 @@ +add_task(async function () { + let loadedPromise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + true + ); + const htmlContent = + "data:text/html, <iframe src='data:text/html,text text'></iframe>"; + BrowserTestUtils.loadURIString(gBrowser, htmlContent); + await loadedPromise; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async 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" + ); + await BrowserTestUtils.synthesizeMouse( + "frame", + 5, + 5, + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + await 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(); + await popupHiddenPromise; +}); 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 0000000000..ecaf7064ab --- /dev/null +++ b/browser/base/content/test/general/browser_bug424101.js @@ -0,0 +1,72 @@ +/* Make sure that the context menu appears on form elements */ + +add_task(async function () { + await 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]; + + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ element: test.element, type: test.type, index }], + async 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" + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#element" + index, + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + await 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(); + await 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 0000000000..29acf0862b --- /dev/null +++ b/browser/base/content/test/general/browser_bug427559.js @@ -0,0 +1,41 @@ +"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.firstElementChild.focus()</script></body>"; + +function getFocusedLocalName(browser) { + return SpecialPowers.spawn(browser, [], async function () { + return content.document.activeElement.localName; + }); +} + +add_task(async function () { + let testTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + + let browser = testTab.linkedBrowser; + + is(await getFocusedLocalName(browser), "button", "button is focused"); + + let blankTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:blank" + ); + + await BrowserTestUtils.switchTab(gBrowser, testTab); + + // Make sure focus is given to the window because the element is now gone. + is(await 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 0000000000..704cd4a675 --- /dev/null +++ b/browser/base/content/test/general/browser_bug431826.js @@ -0,0 +1,56 @@ +function remote(task) { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [], task); +} + +add_task(async function () { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + + let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser, "https://nocert.example.com/"); + await promise; + + await 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" + ); + }); + + await 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 + Services.prefs.setBoolPref("browser.xul.error_pages.expert_bad_cert", true); + + promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + gBrowser.reload(); + await promise; + + await 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 ( + Services.prefs.prefHasUserValue("browser.xul.error_pages.expert_bad_cert") + ) { + Services.prefs.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 0000000000..be4a4b8b5c --- /dev/null +++ b/browser/base/content/test/general/browser_bug432599.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function invokeUsingCtrlD(phase) { + switch (phase) { + case 1: + EventUtils.synthesizeKey("d", { accelKey: true }); + break; + case 2: + case 4: + EventUtils.synthesizeKey("KEY_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("KEY_Escape"); + break; + case 3: + EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star, { clickCount: 2 }); + break; + } +} + +add_task(async function () { + const TEST_URL = "data:text/plain,Content"; + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + registerCleanupFunction(async () => { + await BrowserTestUtils.removeTab(tab); + await PlacesUtils.bookmarks.eraseEverything(); + }); + + // Changing the location causes the star to asynchronously update, thus wait + // for it to be in a stable state before proceeding. + await TestUtils.waitForCondition( + () => BookmarkingUI.status == BookmarkingUI.STATUS_UNSTARRED + ); + + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: TEST_URL, + title: "Bug 432599 Test", + }); + Assert.equal( + BookmarkingUI.status, + BookmarkingUI.STATUS_STARRED, + "The star state should be starred" + ); + + for (let invoker of [invokeUsingStarButton, invokeUsingCtrlD]) { + for (let phase = 1; phase < 5; ++phase) { + let promise = checkBookmarksPanel(phase); + invoker(phase); + await promise; + Assert.equal( + BookmarkingUI.status, + BookmarkingUI.STATUS_STARRED, + "The star state shouldn't change" + ); + } + } +}); + +var initialValue; +var initialRemoveHidden; +async function checkBookmarksPanel(phase) { + StarUI._createPanelIfNeeded(); + let popupElement = document.getElementById("editBookmarkPanel"); + let titleElement = document.getElementById("editBookmarkPanelTitle"); + let removeElement = document.getElementById("editBookmarkPanelRemoveButton"); + await document.l10n.translateElements([titleElement]); + switch (phase) { + case 1: + case 3: + await promisePopupShown(popupElement); + break; + case 2: + initialValue = titleElement.textContent; + initialRemoveHidden = removeElement.hidden; + await promisePopupHidden(popupElement); + break; + case 4: + Assert.equal( + titleElement.textContent, + initialValue, + "The bookmark panel's title should be the same" + ); + Assert.equal( + removeElement.hidden, + initialRemoveHidden, + "The bookmark panel's visibility should not change" + ); + await promisePopupHidden(popupElement); + break; + default: + throw new Error("Unknown phase"); + } +} 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 0000000000..567f655e99 --- /dev/null +++ b/browser/base/content/test/general/browser_bug455852.js @@ -0,0 +1,27 @@ +add_task(async 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; + Services.prefs.setBoolPref("browser.tabs.closeWindowWithLastTab", false); + + EventUtils.synthesizeKey("w", { accelKey: true }); + + 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 (Services.prefs.prefHasUserValue("browser.tabs.closeWindowWithLastTab")) { + Services.prefs.clearUserPref("browser.tabs.closeWindowWithLastTab"); + } +}); 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 0000000000..c8be399639 --- /dev/null +++ b/browser/base/content/test/general/browser_bug462289.js @@ -0,0 +1,144 @@ +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() { + // Put the home button in the pre-proton placement to test focus states. + CustomizableUI.addWidgetToArea( + "home-button", + "nav-bar", + CustomizableUI.getPlacementOfWidget("stop-reload-button").position + 1 + ); + registerCleanupFunction(async function resetToolbar() { + await CustomizableUI.reset(); + }); + + waitForExplicitFinish(); + + tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + setTimeout(step2, 0); +} + +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, {}); + setTimeout(step3, 0); +} + +async 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" + ); + + info("focusing URLBar then sending 3 Shift+Tab."); + gURLBar.focus(); + + let focused = BrowserTestUtils.waitForEvent( + document.getElementById("home-button"), + "focus" + ); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + await focused; + info("Focus is now on Home button"); + + focused = BrowserTestUtils.waitForEvent( + document.getElementById("tabs-newtab-button"), + "focus" + ); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + await focused; + info("Focus is now on the new tab button"); + + focused = BrowserTestUtils.waitForEvent(tab1, "focus"); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + await focused; + 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, {}); + setTimeout(step4, 0); +} + +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" }); + setTimeout(step6, 0); +} + +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 0000000000..fb550cb2b5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug462673.js @@ -0,0 +1,66 @@ +add_task(async function () { + var win = openDialog( + AppConstants.BROWSER_CHROME_URL, + "_blank", + "chrome,all,dialog=no" + ); + await SimpleTest.promiseFocus(win); + + let tab = win.gBrowser.tabs[0]; + await 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.nextElementSibling, + "dependent tab is selected" + ); + win.gBrowser.removeTab(tab); + + // Closing a tab will also close its parent chrome window, but async + await BrowserTestUtils.domWindowClosed(win); +}); + +add_task(async function () { + var win = openDialog( + AppConstants.BROWSER_CHROME_URL, + "_blank", + "chrome,all,dialog=no" + ); + await SimpleTest.promiseFocus(win); + + let tab = win.gBrowser.tabs[0]; + await promiseTabLoadEvent( + tab, + getRootDirectory(gTestPath) + "test_bug462673.html" + ); + + var newTab = BrowserTestUtils.addTab(win.gBrowser); + var newBrowser = newTab.linkedBrowser; + win.gBrowser.removeTab(tab); + ok(!win.closed, "Window stays open"); + if (!win.closed) { + is(win.gBrowser.tabs.length, 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.tabbox.selectedPanel, + newBrowser.parentNode.parentNode.parentNode, + "Panel for remaining tab is selected" + ); + } + + await 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 0000000000..e51f7b48e6 --- /dev/null +++ b/browser/base/content/test/general/browser_bug477014.js @@ -0,0 +1,36 @@ +/* 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(async function () { + let tabToDetach = BrowserTestUtils.addTab(gBrowser, testPage); + await BrowserTestUtils.browserStopped(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); + await BrowserTestUtils.waitForEvent( + 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 0000000000..f616fa0ee4 --- /dev/null +++ b/browser/base/content/test/general/browser_bug479408.js @@ -0,0 +1,23 @@ +function test() { + waitForExplicitFinish(); + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab( + gBrowser, + "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug479408_sample.html" + )); + + BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "DOMLinkAdded", + true + ).then(() => { + executeSoon(function () { + ok( + !tab.linkedBrowser.engines, + "the subframe's search engine wasn't detected" + ); + + gBrowser.removeTab(tab); + finish(); + }); + }); +} 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 0000000000..f83f02bb9d --- /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 0000000000..737ac729a2 --- /dev/null +++ b/browser/base/content/test/general/browser_bug481560.js @@ -0,0 +1,16 @@ +add_task(async function testTabCloseShortcut() { + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + + function onTabClose() { + ok(false, "shouldn't have gotten the TabClose event for the last tab"); + } + var tab = win.gBrowser.selectedTab; + tab.addEventListener("TabClose", onTabClose); + + EventUtils.synthesizeKey("w", { accelKey: true }, win); + + ok(win.closed, "accel+w closed the window immediately"); + + tab.removeEventListener("TabClose", onTabClose); +}); 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 0000000000..21b4e69a33 --- /dev/null +++ b/browser/base/content/test/general/browser_bug484315.js @@ -0,0 +1,14 @@ +add_task(async function test() { + window.open("about:blank", "", "width=100,height=100,noopener"); + + let win = Services.wm.getMostRecentWindow("navigator:browser"); + Services.prefs.setBoolPref("browser.tabs.closeWindowWithLastTab", false); + win.gBrowser.removeCurrentTab(); + ok(win.closed, "popup is closed"); + + // clean up + if (!win.closed) { + win.close(); + } + Services.prefs.clearUserPref("browser.tabs.closeWindowWithLastTab"); +}); 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 0000000000..d8eaa15f45 --- /dev/null +++ b/browser/base/content/test/general/browser_bug491431.js @@ -0,0 +1,42 @@ +/* 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 = BrowserTestUtils.addTab(gBrowser, testPage); + gBrowser.tabContainer.addEventListener( + "TabClose", + function (firstTabCloseEvent) { + ok(!firstTabCloseEvent.detail.adoptedBy, "This was a normal tab close"); + + // test tab close by moving + tabB = BrowserTestUtils.addTab(gBrowser, testPage); + gBrowser.tabContainer.addEventListener( + "TabClose", + function (secondTabCloseEvent) { + executeSoon(function () { + ok( + secondTabCloseEvent.detail.adoptedBy, + "This was a tab closed by moving" + ); + + // cleanup + newWin.close(); + executeSoon(finish); + }); + }, + { capture: true, once: true } + ); + newWin = gBrowser.replaceTabWithWindow(tabB); + }, + { capture: true, once: 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 0000000000..95a444bf6a --- /dev/null +++ b/browser/base/content/test/general/browser_bug495058.js @@ -0,0 +1,53 @@ +/** + * Tests that the right elements of a tab are focused when it is + * torn out into its own window. + */ + +const URIS = [ + "about:blank", + "about:home", + "about:sessionrestore", + "about:privatebrowsing", +]; + +add_task(async function () { + for (let uri of URIS) { + let tab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.loadURIString(tab.linkedBrowser, uri); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + let isRemote = tab.linkedBrowser.isRemoteBrowser; + + let win = gBrowser.replaceTabWithWindow(tab); + + await TestUtils.topicObserved( + "browser-delayed-startup-finished", + subject => subject == win + ); + // In the e10s case, we wait for the content to first paint before we focus + // the URL in the new window, to optimize for content paint time. + if (isRemote) { + await win.gBrowserInit.firstContentWindowPaintPromise; + } + + tab = win.gBrowser.selectedTab; + + Assert.equal( + win.gBrowser.currentURI.spec, + uri, + uri + ": uri loaded in detached tab" + ); + + const expectedActiveElement = tab.isEmpty + ? win.gURLBar.inputField + : win.gBrowser.selectedBrowser; + Assert.equal( + win.document.activeElement, + expectedActiveElement, + `${uri}: the active element is expected: ${win.document.activeElement?.nodeName}` + ); + Assert.equal(win.gURLBar.value, "", uri + ": urlbar is empty"); + Assert.ok(win.gURLBar.placeholder, uri + ": placeholder text is present"); + + await BrowserTestUtils.closeWindow(win); + } +}); 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 0000000000..d83d082556 --- /dev/null +++ b/browser/base/content/test/general/browser_bug519216.js @@ -0,0 +1,48 @@ +function test() { + waitForExplicitFinish(); + gBrowser.addProgressListener(progressListener1); + gBrowser.addProgressListener(progressListener2); + gBrowser.addProgressListener(progressListener3); + BrowserTestUtils.loadURIString(gBrowser, "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 0000000000..234747fcbf --- /dev/null +++ b/browser/base/content/test/general/browser_bug520538.js @@ -0,0 +1,27 @@ +function test() { + var tabCount = gBrowser.tabs.length; + gBrowser.selectedBrowser.focus(); + window.browserDOMWindow.openURI( + makeURI("about:blank"), + null, + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL, + Services.scriptSecurityManager.getSystemPrincipal() + ); + 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 0000000000..8c885bbcc8 --- /dev/null +++ b/browser/base/content/test/general/browser_bug521216.js @@ -0,0 +1,68 @@ +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); + BrowserTestUtils.addTab( + gBrowser, + "data:text/html,<html><head><link href='about:logo' rel='shortcut icon'>" + ); +} + +function recordEvent(aName) { + info("got " + aName); + if (!actual.includes(aName)) { + actual.push(aName); + } + if (actual.length == expected.length) { + is( + actual.toString(), + expected.toString(), + "got events and progress notifications in expected order" + ); + + executeSoon( + // eslint-disable-next-line no-shadow + function (tab) { + gBrowser.removeTab(tab); + gBrowser.removeTabsProgressListener(progressListener); + gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen); + finish(); + }.bind(null, tab) + ); + } +} + +function TabOpen(aEvent) { + if (aEvent.target == tab) { + recordEvent("TabOpen"); + } +} + +var progressListener = { + onLocationChange: function onLocationChange(aBrowser) { + if (aBrowser == tab.linkedBrowser) { + recordEvent("onLocationChange"); + } + }, + onStateChange: function onStateChange(aBrowser) { + if (aBrowser == tab.linkedBrowser) { + recordEvent("onStateChange"); + } + }, + onLinkIconAvailable: function onLinkIconAvailable(aBrowser) { + if (aBrowser == tab.linkedBrowser) { + recordEvent("onLinkIconAvailable"); + } + }, +}; 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 0000000000..7f6225b519 --- /dev/null +++ b/browser/base/content/test/general/browser_bug533232.js @@ -0,0 +1,56 @@ +function test() { + var tab1 = gBrowser.selectedTab; + var tab2 = BrowserTestUtils.addTab(gBrowser); + var childTab1; + var childTab2; + + childTab1 = BrowserTestUtils.addTab(gBrowser, "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 = BrowserTestUtils.addTab(gBrowser, "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 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + relatedToCurrent: true, + }); + childTab2 = BrowserTestUtils.addTab(gBrowser, "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.prototype.indexOf.call(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 0000000000..5c871a759c --- /dev/null +++ b/browser/base/content/test/general/browser_bug537013.js @@ -0,0 +1,168 @@ +/* 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 HasFindClipboard = Services.clipboard.isClipboardTypeSupported( + Services.clipboard.kFindClipboard +); + +function addTabWithText(aText, aCallback) { + let newTab = BrowserTestUtils.addTab( + gBrowser, + "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]; + + gBrowser.getFindBar().then(initialTest); +} + +function initialTest() { + setFindString(texts[0]); + // Turn on highlight for testing bug 891638 + gFindBar.getElement("highlight").checked = true; + + // Make sure the second tab is correct, then set it up + gBrowser.selectedTab = tabs[1]; + gBrowser.selectedTab.addEventListener("TabFindInitialized", continueTests1, { + once: true, + }); + // Initialize the findbar + gBrowser.getFindBar(); +} +function 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 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]; + gBrowser.selectedTab.addEventListener("TabFindInitialized", continueTests4, { + once: true, + }); + gBrowser.getFindBar(); +} + +function continueTests4() { + 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 0000000000..b890bf2fea --- /dev/null +++ b/browser/base/content/test/general/browser_bug537474.js @@ -0,0 +1,20 @@ +add_task(async function () { + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + "about:mozilla" + ); + window.browserDOMWindow.openURI( + makeURI("about:mozilla"), + null, + Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, + null, + Services.scriptSecurityManager.getSystemPrincipal() + ); + await browserLoadedPromise; + is( + gBrowser.currentURI.spec, + "about:mozilla", + "page loads in the current content window" + ); +}); 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 0000000000..26c8fd1767 --- /dev/null +++ b/browser/base/content/test/general/browser_bug563588.js @@ -0,0 +1,42 @@ +function press(key, expectedPos) { + var originalSelectedTab = gBrowser.selectedTab; + EventUtils.synthesizeKey("VK_" + key.toUpperCase(), { + accelKey: true, + shiftKey: true, + }); + is( + gBrowser.selectedTab, + originalSelectedTab, + "shift+accel+" + key + " doesn't change which tab is selected" + ); + is( + gBrowser.tabContainer.selectedIndex, + expectedPos, + "shift+accel+" + key + " moves the tab to the expected position" + ); + is( + document.activeElement, + gBrowser.selectedTab, + "shift+accel+" + key + " leaves the selected tab focused" + ); +} + +function test() { + BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.addTab(gBrowser); + 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 0000000000..6176c537e3 --- /dev/null +++ b/browser/base/content/test/general/browser_bug565575.js @@ -0,0 +1,21 @@ +add_task(async function () { + gBrowser.selectedBrowser.focus(); + + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => BrowserOpenTab(), + false + ); + ok(gURLBar.focused, "location bar is focused for a new tab"); + + await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]); + ok( + !gURLBar.focused, + "location bar isn't focused for the previously selected tab" + ); + + await 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 0000000000..3d3e47e17d --- /dev/null +++ b/browser/base/content/test/general/browser_bug567306.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var HasFindClipboard = Services.clipboard.isClipboardTypeSupported( + Services.clipboard.kFindClipboard +); + +add_task(async function () { + let newwindow = await BrowserTestUtils.openNewBrowserWindow(); + + let selectedBrowser = newwindow.gBrowser.selectedBrowser; + await new Promise((resolve, reject) => { + BrowserTestUtils.waitForContentEvent( + selectedBrowser, + "pageshow", + true, + event => { + return event.target.location != "about:blank"; + } + ).then(function pageshowListener() { + ok( + true, + "pageshow listener called: " + newwindow.gBrowser.currentURI.spec + ); + resolve(); + }); + selectedBrowser.loadURI( + Services.io.newURI("data:text/html,<h1 id='h1'>Select Me</h1>"), + { + triggeringPrincipal: + Services.scriptSecurityManager.getSystemPrincipal(), + } + ); + }); + + await SimpleTest.promiseFocus(newwindow); + + ok(!newwindow.gFindBarInitialized, "find bar is not yet initialized"); + let findBar = await newwindow.gFindBarPromise; + + await SpecialPowers.spawn(selectedBrowser, [], async 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); + }); + + await 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(); + await 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 0000000000..a429cdf5c7 --- /dev/null +++ b/browser/base/content/test/general/browser_bug575561.js @@ -0,0 +1,118 @@ +requestLongerTimeout(2); + +const TEST_URL = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/general/app_bug575561.html"; + +add_task(async function () { + SimpleTest.requestCompleteLog(); + + // allow top level data: URI navigations, otherwise clicking data: link fails + await SpecialPowers.pushPrefEnv({ + set: [["security.data_uri.block_toplevel_data_uri_navigations", false]], + }); + + // 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 + await 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 + await 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 + await 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 + await 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" + await 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 + await 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 + await 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> + await testLink(5, true, false); + + // Pinned: Link to an about: URI should not open a new tab + // Tests link to about:logo + await 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" + ); +}); + +async function testLink( + aLinkIndexOrFunction, + pinTab, + expectNewTab, + testSubFrame, + aURL = TEST_URL +) { + let appTab = BrowserTestUtils.addTab(gBrowser, aURL, { skipAnimation: true }); + if (pinTab) { + gBrowser.pinTab(appTab); + } + gBrowser.selectedTab = appTab; + + let browser = appTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(browser); + + let promise; + if (expectNewTab) { + promise = BrowserTestUtils.waitForNewTab(gBrowser).then(tab => { + let loaded = tab.linkedBrowser.documentURI.spec; + BrowserTestUtils.removeTab(tab); + return loaded; + }); + } else { + promise = BrowserTestUtils.browserLoaded(browser, testSubFrame); + } + + let href; + if (typeof aLinkIndexOrFunction === "function") { + ok(!browser.isRemoteBrowser, "don't pass a function for a remote browser"); + let link = aLinkIndexOrFunction(browser.contentDocument); + info("Clicking " + link.textContent); + link.click(); + href = link.href; + } else { + href = await SpecialPowers.spawn( + browser, + [[testSubFrame, aLinkIndexOrFunction]], + function ([subFrame, index]) { + let doc = subFrame + ? content.document.querySelector("iframe").contentDocument + : content.document; + let link = doc.querySelectorAll("a")[index]; + + info("Clicking " + link.textContent); + link.click(); + return link.href; + } + ); + } + + info(`Waiting on load of ${href}`); + let loaded = await promise; + is(loaded, href, "loaded the right document"); + BrowserTestUtils.removeTab(appTab); +} 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 0000000000..cbaa379e85 --- /dev/null +++ b/browser/base/content/test/general/browser_bug577121.js @@ -0,0 +1,27 @@ +/* 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() { + // Disable tab animations + gReduceMotionOverride = true; + + // Open 2 other tabs, and pin the second one. Like that, the initial tab + // should get closed. + let testTab1 = BrowserTestUtils.addTab(gBrowser); + let testTab2 = BrowserTestUtils.addTab(gBrowser); + 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 0000000000..04b5fe9cfd --- /dev/null +++ b/browser/base/content/test/general/browser_bug578534.js @@ -0,0 +1,31 @@ +/* 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 { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +add_task(async function test() { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let uriString = "http://example.com/"; + let cookieBehavior = "network.cookie.cookieBehavior"; + + await SpecialPowers.pushPrefEnv({ set: [[cookieBehavior, 2]] }); + PermissionTestUtils.add(uriString, "cookie", Services.perms.ALLOW_ACTION); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: uriString }, + async function (browser) { + await SpecialPowers.spawn(browser, [], function () { + is( + content.navigator.cookieEnabled, + true, + "navigator.cookieEnabled should be true" + ); + }); + } + ); + + PermissionTestUtils.add(uriString, "cookie", Services.perms.UNKNOWN_ACTION); +}); 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 0000000000..47de7ea240 --- /dev/null +++ b/browser/base/content/test/general/browser_bug579872.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/. */ + +add_task(async function () { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = BrowserTestUtils.addTab(gBrowser, "http://example.com"); + await BrowserTestUtils.browserLoaded(newTab.linkedBrowser); + + gBrowser.pinTab(newTab); + gBrowser.selectedTab = newTab; + + openTrustedLinkIn("javascript:var x=0;", "current"); + is(gBrowser.tabs.length, 2, "Should open in current tab"); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + openTrustedLinkIn("http://example.com/1", "current"); + is(gBrowser.tabs.length, 2, "Should open in current tab"); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + openTrustedLinkIn("http://example.org/", "current"); + is(gBrowser.tabs.length, 3, "Should open in new tab"); + + await BrowserTestUtils.removeTab(newTab); + await BrowserTestUtils.removeTab(gBrowser.tabs[1]); // example.org tab +}); 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 0000000000..a901ce96e1 --- /dev/null +++ b/browser/base/content/test/general/browser_bug581253.js @@ -0,0 +1,74 @@ +/* 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"; + +add_task(async function test_remove_bookmark_with_tag_via_edit_bookmark() { + waitForExplicitFinish(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + registerCleanupFunction(async function () { + await PlacesUtils.bookmarks.eraseEverything(); + BrowserTestUtils.removeTab(tab); + await PlacesUtils.history.clear(); + }); + + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "", + url: testURL, + }); + + Assert.ok( + await PlacesUtils.bookmarks.fetch({ url: testURL }), + "the test url is bookmarked" + ); + + BrowserTestUtils.loadURIString(gBrowser, testURL); + + await TestUtils.waitForCondition( + () => BookmarkingUI.status == BookmarkingUI.STATUS_STARRED, + "star button indicates that the page is bookmarked" + ); + + PlacesUtils.tagging.tagURI(makeURI(testURL), [testTag]); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + StarUI.panel, + "popupshown" + ); + + BookmarkingUI.star.click(); + + await popupShownPromise; + + let tagsField = document.getElementById("editBMPanel_tagsField"); + Assert.ok(tagsField.value == testTag, "tags field value was set"); + tagsField.focus(); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + StarUI.panel, + "popuphidden" + ); + + let removeNotification = PlacesTestUtils.waitForNotification( + "bookmark-removed", + events => events.some(event => unescape(event.url) == testURL) + ); + + let removeButton = document.getElementById("editBookmarkPanelRemoveButton"); + removeButton.click(); + + await popupHiddenPromise; + + await removeNotification; + + is( + BookmarkingUI.status, + BookmarkingUI.STATUS_UNSTARRED, + "star button indicates that the bookmark has been removed" + ); +}); 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 0000000000..23e0c5ddf5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug585785.js @@ -0,0 +1,48 @@ +var tab; + +function test() { + waitForExplicitFinish(); + + // Force-enable tab animations + gReduceMotionOverride = false; + + tab = BrowserTestUtils.addTab(gBrowser); + 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( + "TabAnimationEnd", + function listener() { + executeSoon(function () { + ok(!tab.parentNode, "tab removed asynchronously"); + finish(); + }); + }, + { once: true } + ); +} 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 0000000000..2267a8b2ac --- /dev/null +++ b/browser/base/content/test/general/browser_bug585830.js @@ -0,0 +1,27 @@ +/* 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 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + BrowserTestUtils.addTab(gBrowser); + 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_bug594131.js b/browser/base/content/test/general/browser_bug594131.js new file mode 100644 index 0000000000..db06b69425 --- /dev/null +++ b/browser/base/content/test/general/browser_bug594131.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() { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = BrowserTestUtils.addTab(gBrowser, "http://example.com"); + waitForExplicitFinish(); + BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart); + + function mainPart() { + gBrowser.pinTab(newTab); + gBrowser.selectedTab = newTab; + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + openTrustedLinkIn("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_bug596687.js b/browser/base/content/test/general/browser_bug596687.js new file mode 100644 index 0000000000..8c68cd5a03 --- /dev/null +++ b/browser/base/content/test/general/browser_bug596687.js @@ -0,0 +1,28 @@ +add_task(async function test() { + var tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + var gotTabAttrModified = false; + var gotTabClose = false; + + function onTabClose() { + gotTabClose = true; + tab.addEventListener("TabAttrModified", onTabAttrModified); + } + + function onTabAttrModified() { + gotTabAttrModified = true; + } + + tab.addEventListener("TabClose", onTabClose); + + 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); + tab.removeEventListener("TabAttrModified", onTabAttrModified); +}); 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 0000000000..963912c9da --- /dev/null +++ b/browser/base/content/test/general/browser_bug597218.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/. */ + +function test() { + waitForExplicitFinish(); + + // establish initial state + is(gBrowser.tabs.length, 1, "we start with one tab"); + + // create a tab + let tab = gBrowser.addTab("about:blank", { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + 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 0000000000..8195eba4ec --- /dev/null +++ b/browser/base/content/test/general/browser_bug609700.js @@ -0,0 +1,28 @@ +function test() { + waitForExplicitFinish(); + + Services.ww.registerNotification(function notification( + aSubject, + aTopic, + aData + ) { + if (aTopic == "domwindowopened") { + Services.ww.unregisterNotification(notification); + + 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 0000000000..27751e74ad --- /dev/null +++ b/browser/base/content/test/general/browser_bug623893.js @@ -0,0 +1,50 @@ +add_task(async function test() { + await BrowserTestUtils.withNewTab( + "data:text/plain;charset=utf-8,1", + async function (browser) { + BrowserTestUtils.loadURIString( + browser, + "data:text/plain;charset=utf-8,2" + ); + await BrowserTestUtils.browserLoaded(browser); + + BrowserTestUtils.loadURIString( + browser, + "data:text/plain;charset=utf-8,3" + ); + await BrowserTestUtils.browserLoaded(browser); + + await duplicate(0, "maintained the original index"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + await duplicate(-1, "went back"); + await duplicate(1, "went forward"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } + ); +}); + +async function promiseGetIndex(browser) { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + return SpecialPowers.spawn(browser, [], function () { + let shistory = + docShell.browsingContext.childSessionHistory.legacySHistory; + return shistory.index; + }); + } + + let shistory = browser.browsingContext.sessionHistory; + return shistory.index; +} + +let duplicate = async function (delta, msg, cb) { + var startIndex = await promiseGetIndex(gBrowser.selectedBrowser); + + duplicateTabIn(gBrowser.selectedTab, "tab", delta); + + await BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored"); + + let endIndex = await 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 0000000000..962c62c5d8 --- /dev/null +++ b/browser/base/content/test/general/browser_bug624734.js @@ -0,0 +1,49 @@ +/* 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() { + let elem = document.getElementById("context-bookmarkpage"); + let l10n = document.l10n.getAttributes(elem); + ok( + [ + "main-context-menu-bookmark-page", + "main-context-menu-bookmark-page-with-shortcut", + "main-context-menu-bookmark-page-mac", + ].includes(l10n.id) + ); + + gBrowser.removeCurrentTab(); + finish(); +} + +function test() { + waitForExplicitFinish(); + + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + CustomizableUI.addWidgetToArea( + "bookmarks-menu-button", + CustomizableUI.AREA_NAVBAR, + 0 + ); + 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 { + CustomizableUI.removeWidgetFromArea("bookmarks-menu-button"); + finishTest(); + } + }); + + BrowserTestUtils.loadURIString( + tab.linkedBrowser, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/general/dummy_page.html" + ); +} 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 0000000000..4f9dbcea9f --- /dev/null +++ b/browser/base/content/test/general/browser_bug664672.js @@ -0,0 +1,27 @@ +function test() { + waitForExplicitFinish(); + + var tab = BrowserTestUtils.addTab(gBrowser); + + tab.addEventListener( + "TabClose", + function () { + 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(); + }); + }, + { once: true } + ); + + 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 0000000000..24d8d88447 --- /dev/null +++ b/browser/base/content/test/general/browser_bug676619.js @@ -0,0 +1,225 @@ +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +function waitForNewWindow() { + return new Promise(resolve => { + var listener = { + onOpenWindow: aXULWindow => { + info("Download window shown..."); + Services.wm.removeListener(listener); + + function downloadOnLoad() { + domwindow.removeEventListener("load", downloadOnLoad, true); + + is( + domwindow.document.location.href, + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + "Download page appeared" + ); + resolve(domwindow); + } + + var domwindow = aXULWindow.docShell.domWindow; + domwindow.addEventListener("load", downloadOnLoad, true); + }, + onCloseWindow: aXULWindow => {}, + }; + + Services.wm.addListener(listener); + registerCleanupFunction(() => { + try { + Services.wm.removeListener(listener); + } catch (e) {} + }); + }); +} + +async function waitForFilePickerTest(link, name) { + let filePickerShownPromise = new Promise(resolve => { + MockFilePicker.showCallback = function (fp) { + ok(true, "Filepicker shown."); + is(name, fp.defaultString, " filename matches download name"); + setTimeout(resolve, 0); + return Ci.nsIFilePicker.returnCancel; + }; + }); + + SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => { + content.document.getElementById(contentLink).click(); + }); + + await filePickerShownPromise; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + Assert.equal( + content.document.getElementById("unload-flag").textContent, + "Okay", + "beforeunload shouldn't have fired" + ); + }); +} + +async function testLink(link, name) { + info("Checking " + link + " with name: " + name); + + if ( + Services.prefs.getBoolPref( + "browser.download.always_ask_before_handling_new_types", + false + ) + ) { + let winPromise = waitForNewWindow(); + + SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => { + content.document.getElementById(contentLink).click(); + }); + + let win = await winPromise; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + Assert.equal( + content.document.getElementById("unload-flag").textContent, + "Okay", + "beforeunload shouldn't have fired" + ); + }); + + is( + win.document.getElementById("location").value, + name, + `file name should match (${link})` + ); + + await BrowserTestUtils.closeWindow(win); + } else { + await waitForFilePickerTest(link, name); + } +} + +// Cross-origin URL does not trigger a download +async function testLocation(link, url) { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + + SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => { + content.document.getElementById(contentLink).click(); + }); + + let tab = await tabPromise; + BrowserTestUtils.removeTab(tab); +} + +async function runTest(url) { + let tab = BrowserTestUtils.addTab(gBrowser, url); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + await testLink("link1", "test.txt"); + await testLink("link2", "video.ogg"); + await testLink("link3", "just some video.ogg"); + await testLink("link4", "with-target.txt"); + await testLink("link5", "javascript.html"); + await testLink("link6", "test.blob"); + await testLink("link7", "test.file"); + await testLink("link8", "download_page_3.txt"); + await testLink("link9", "download_page_3.txt"); + await testLink("link10", "download_page_4.txt"); + await testLink("link11", "download_page_4.txt"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await testLocation("link12", "http://example.com/"); + + // Check that we enforce the correct extension if the website's + // is bogus or missing. These extensions can differ slightly (ogx vs ogg, + // htm vs html) on different OSes. + let oggExtension = getMIMEInfoForType("application/ogg").primaryExtension; + await testLink("link13", "no file extension." + oggExtension); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1690051#c8 + if (AppConstants.platform != "win") { + const PREF = "browser.download.sanitize_non_media_extensions"; + ok(Services.prefs.getBoolPref(PREF), "pref is set before"); + + // Check that ics (iCal) extension is changed/fixed when the pref is true. + await testLink("link14", "dummy.ics"); + + // And not changed otherwise. + Services.prefs.setBoolPref(PREF, false); + await testLink("link14", "dummy.not-ics"); + Services.prefs.clearUserPref(PREF); + } + + await testLink("link15", "download_page_3.txt"); + await testLink("link16", "download_page_3.txt"); + await testLink("link17", "download_page_4.txt"); + await testLink("link18", "download_page_4.txt"); + await testLink("link19", "download_page_4.txt"); + await testLink("link20", "download_page_4.txt"); + await testLink("link21", "download_page_4.txt"); + await testLink("link22", "download_page_4.txt"); + + BrowserTestUtils.removeTab(tab); +} + +async function setDownloadDir() { + let tmpDir = PathUtils.join( + PathUtils.tempDir, + "testsavedir" + Math.floor(Math.random() * 2 ** 32) + ); + // Create this dir if it doesn't exist (ignores existing dirs) + await IOUtils.makeDirectory(tmpDir); + registerCleanupFunction(async function () { + try { + await IOUtils.remove(tmpDir, { recursive: true }); + } catch (e) { + console.error(e); + } + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + }); + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", tmpDir); +} + +add_task(async function () { + requestLongerTimeout(3); + waitForExplicitFinish(); + + await setDownloadDir(); + + info( + "Test with browser.download.always_ask_before_handling_new_types enabled." + ); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", true], + ["browser.download.useDownloadDir", true], + ], + }); + + await runTest( + "http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html" + ); + await runTest( + "https://example.com:443/browser/browser/base/content/test/general/download_page.html" + ); + + info( + "Test with browser.download.always_ask_before_handling_new_types disabled." + ); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.useDownloadDir", false], + ], + }); + + await runTest( + "http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html" + ); + await runTest( + "https://example.com:443/browser/browser/base/content/test/general/download_page.html" + ); + + MockFilePicker.cleanup(); +}); 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 0000000000..a91f8f9a1e --- /dev/null +++ b/browser/base/content/test/general/browser_bug710878.js @@ -0,0 +1,49 @@ +/* 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(async function () { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function (browser) { + let contextMenu = document.getElementById("contentAreaContextMenu"); + let awaitPopupShown = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + let awaitPopupHidden = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "a", + { + type: "contextmenu", + button: 2, + }, + browser + ); + + await awaitPopupShown; + + is( + gContextMenu.linkTextStr, + "word1 word2 word3", + "Text under link is correctly computed." + ); + + contextMenu.hidePopup(); + await awaitPopupHidden; + } + ); +}); 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 0000000000..78290e21f5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug724239.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { TabStateFlusher } = ChromeUtils.importESModule( + "resource:///modules/sessionstore/TabStateFlusher.sys.mjs" +); + +add_task(async function test_blank() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + BrowserTestUtils.loadURIString(browser, "http://example.com"); + await BrowserTestUtils.browserLoaded(browser); + ok(!gBrowser.canGoBack, "about:blank wasn't added to session history"); + } + ); +}); + +add_task(async function test_newtab() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // Can't load it directly because that'll use a preloaded tab if present. + let stopped = BrowserTestUtils.browserStopped(browser, "about:newtab"); + BrowserTestUtils.loadURIString(browser, "about:newtab"); + await stopped; + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + stopped = BrowserTestUtils.browserStopped(browser, "http://example.com/"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + BrowserTestUtils.loadURIString(browser, "http://example.com/"); + await stopped; + + // This makes sure the parent process has the most up-to-date notion + // of the tab's session history. + await TabStateFlusher.flush(browser); + + let tab = gBrowser.getTabForBrowser(browser); + let tabState = JSON.parse(SessionStore.getTabState(tab)); + Assert.equal( + tabState.entries.length, + 2, + "We should have 2 entries in the session history." + ); + + Assert.equal( + tabState.entries[0].url, + "about:newtab", + "about:newtab should be the first entry." + ); + + Assert.ok(gBrowser.canGoBack, "Should be able to browse back."); + } + ); +}); 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 0000000000..9e7bcf5977 --- /dev/null +++ b/browser/base/content/test/general/browser_bug734076.js @@ -0,0 +1,195 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function () { + // allow top level data: URI navigations, otherwise loading data: URIs + // in toplevel windows fail. + await SpecialPowers.pushPrefEnv({ + set: [["security.data_uri.block_toplevel_data_uri_navigations", false]], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, null, false); + + tab.linkedBrowser.stop(); // stop the about:blank load + + let writeDomainURL = encodeURI( + "data:text/html,<script>document.write(document.domain);</script>" + ); + + let tests = [ + { + name: "view image with background image", + url: "http://mochi.test:8888/", + element: "body", + opensNewTab: true, + go() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ writeDomainURL }], + async function (arg) { + let contentBody = content.document.body; + contentBody.style.backgroundImage = + "url('" + arg.writeDomainURL + "')"; + + return "context-viewimage"; + } + ); + }, + verify(browser) { + return SpecialPowers.spawn(browser, [], async function (arg) { + Assert.equal( + content.document.body.textContent, + "", + "no domain was inherited for view image with background image" + ); + }); + }, + }, + { + name: "view image", + url: "http://mochi.test:8888/", + element: "img", + opensNewTab: true, + go() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ writeDomainURL }], + async 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.firstElementChild); + + return "context-viewimage"; + } + ); + }, + verify(browser) { + return SpecialPowers.spawn(browser, [], async function (arg) { + Assert.equal( + content.document.body.textContent, + "", + "no domain was inherited for view image" + ); + }); + }, + }, + { + name: "show only this frame", + url: "http://mochi.test:8888/", + element: "html", + frameIndex: 0, + go() { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ writeDomainURL }], + async function (arg) { + let doc = content.document; + let iframe = doc.createElement("iframe"); + iframe.setAttribute("src", arg.writeDomainURL); + doc.body.insertBefore(iframe, doc.body.firstElementChild); + + // Wait for the iframe to load. + return new Promise(resolve => { + iframe.addEventListener( + "load", + function () { + resolve("context-showonlythisframe"); + }, + { capture: true, once: true } + ); + }); + } + ); + }, + verify(browser) { + return SpecialPowers.spawn(browser, [], async function (arg) { + Assert.equal( + 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 + ); + BrowserTestUtils.loadURIString(gBrowser, test.url); + await loadedPromise; + + info("Run subtest " + test.name); + let commandToRun = await test.go(); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + contentAreaContextMenu, + "popupshown" + ); + + let browsingContext = gBrowser.selectedBrowser.browsingContext; + if (test.frameIndex != null) { + browsingContext = browsingContext.children[test.frameIndex]; + } + + await new Promise(r => { + SimpleTest.executeSoon(r); + }); + + // Sometimes, the iframe test fails as the child iframe hasn't finishing layout + // yet. Try again in this case. + while (true) { + try { + await BrowserTestUtils.synthesizeMouse( + test.element, + 3, + 3, + { type: "contextmenu", button: 2 }, + browsingContext + ); + } catch (ex) { + continue; + } + break; + } + + await popupShownPromise; + info("onImage: " + gContextMenu.onImage); + + let loadedAfterCommandPromise = test.opensNewTab + ? BrowserTestUtils.waitForNewTab(gBrowser, null, true) + : BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + contentAreaContextMenu, + "popuphidden" + ); + if (commandToRun == "context-showonlythisframe") { + let subMenu = document.getElementById("frame"); + let subMenuShown = BrowserTestUtils.waitForEvent(subMenu, "popupshown"); + subMenu.openMenu(true); + await subMenuShown; + } + contentAreaContextMenu.activateItem(document.getElementById(commandToRun)); + let result = await loadedAfterCommandPromise; + + await test.verify( + test.opensNewTab ? result.linkedBrowser : gBrowser.selectedBrowser + ); + + await popupHiddenPromise; + + if (test.opensNewTab) { + gBrowser.removeCurrentTab(); + } + } + + gBrowser.removeCurrentTab(); +}); 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 0000000000..4430e5d8a7 --- /dev/null +++ b/browser/base/content/test/general/browser_bug749738.js @@ -0,0 +1,32 @@ +/* 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 = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org/browser/browser/base/content/test/general/dummy_page.html"; + +/** + * This test checks that if you search for something on one tab, then close + * that tab and have the find bar open on the next tab you get switched to, + * closing the find bar in that tab works without exceptions. + */ +add_task(async function test_bug749738() { + // Open find bar on initial tab. + await gFindBarPromise; + + await BrowserTestUtils.withNewTab(DUMMY_PAGE, async function () { + await gFindBarPromise; + gFindBar.onFindCommand(); + EventUtils.sendString("Dummy"); + }); + + try { + gFindBar.close(); + ok(true, "findbar.close should not throw an exception"); + } catch (e) { + ok(false, "findbar.close threw exception: " + e); + } +}); 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 0000000000..bed03561ca --- /dev/null +++ b/browser/base/content/test/general/browser_bug763468_perwindowpb.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/. */ +"use strict"; + +// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing +add_task(async function testPBNewTab() { + registerCleanupFunction(async function () { + for (let win of windowsToClose) { + await BrowserTestUtils.closeWindow(win); + } + }); + + let windowsToClose = []; + + async function doTest(aIsPrivateMode) { + let newTabURL; + let mode; + let win = await BrowserTestUtils.openNewBrowserWindow({ + private: aIsPrivateMode, + }); + windowsToClose.push(win); + + if (aIsPrivateMode) { + mode = "per window private browsing"; + newTabURL = "about:privatebrowsing"; + } else { + mode = "normal"; + newTabURL = "about:newtab"; + } + await openNewTab(win, newTabURL); + + is( + win.gBrowser.currentURI.spec, + newTabURL, + "URL of NewTab should be " + newTabURL + " in " + mode + " mode" + ); + } + + await doTest(false); + await doTest(true); + await doTest(false); +}); + +async function openNewTab(aWindow, aExpectedURL) { + // Open a new tab + aWindow.BrowserOpenTab(); + let browser = aWindow.gBrowser.selectedBrowser; + + // We're already loaded. + if (browser.currentURI.spec === aExpectedURL) { + return; + } + + // Wait for any location change. + await BrowserTestUtils.waitForLocationChange(aWindow.gBrowser); +} 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 0000000000..7fcc6ad565 --- /dev/null +++ b/browser/base/content/test/general/browser_bug767836_perwindowpb.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/. */ +"use strict"; + +async function doTest(isPrivate) { + let win = await BrowserTestUtils.openNewBrowserWindow({ private: isPrivate }); + let defaultURL = AboutNewTab.newTabURL; + let newTabURL; + let mode; + let testURL = "https://example.com/"; + if (isPrivate) { + mode = "per window private browsing"; + newTabURL = "about:privatebrowsing"; + } else { + mode = "normal"; + newTabURL = "about:newtab"; + } + + await openNewTab(win, newTabURL); + // Check the new tab opened while in normal/private mode + is( + win.gBrowser.selectedBrowser.currentURI.spec, + newTabURL, + "URL of NewTab should be " + newTabURL + " in " + mode + " mode" + ); + + // Set the custom newtab url + AboutNewTab.newTabURL = testURL; + is(AboutNewTab.newTabURL, testURL, "Custom newtab url is set"); + + // Open a newtab after setting the custom newtab url + await openNewTab(win, testURL); + is( + win.gBrowser.selectedBrowser.currentURI.spec, + testURL, + "URL of NewTab should be the custom url" + ); + + // Clear the custom url. + AboutNewTab.resetNewTabURL(); + is(AboutNewTab.newTabURL, defaultURL, "No custom newtab url is set"); + + win.gBrowser.removeTab(win.gBrowser.selectedTab); + win.gBrowser.removeTab(win.gBrowser.selectedTab); + await BrowserTestUtils.closeWindow(win); +} + +add_task(async function test_newTabService() { + // check whether any custom new tab url has been configured + ok(!AboutNewTab.newTabURLOverridden, "No custom newtab url is set"); + + // test normal mode + await doTest(false); + + // test private mode + await doTest(true); +}); + +async function openNewTab(aWindow, aExpectedURL) { + // Open a new tab + aWindow.BrowserOpenTab(); + let browser = aWindow.gBrowser.selectedBrowser; + + // We're already loaded. + if (browser.currentURI.spec === aExpectedURL) { + return; + } + + // Wait for any location change. + await BrowserTestUtils.waitForLocationChange(aWindow.gBrowser); +} 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 0000000000..eba54aea5b --- /dev/null +++ b/browser/base/content/test/general/browser_bug817947.js @@ -0,0 +1,51 @@ +/* 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"; + +add_task(async () => { + Services.prefs.setBoolPref(PREF, true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF); + }); + + let tab = await preparePendingTab(); + + let deferredTab = PromiseUtils.defer(); + + let win = gBrowser.replaceTabWithWindow(tab); + win.addEventListener( + "before-initial-tab-adopted", + async () => { + let [newTab] = win.gBrowser.tabs; + await BrowserTestUtils.browserLoaded(newTab.linkedBrowser); + deferredTab.resolve(newTab); + }, + { once: true } + ); + + let newTab = await deferredTab.promise; + is(newTab.linkedBrowser.currentURI.spec, URL, "correct url should be loaded"); + ok(!newTab.hasAttribute("pending"), "tab should not be pending"); + + win.close(); +}); + +async function preparePendingTab(aCallback) { + let tab = BrowserTestUtils.addTab(gBrowser, URL); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab); + BrowserTestUtils.removeTab(tab); + await sessionUpdatePromise; + + let [{ state }] = SessionStore.getClosedTabDataForWindow(window); + + tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + SessionStore.setTabState(tab, JSON.stringify(state)); + ok(tab.hasAttribute("pending"), "tab should be pending"); + + return tab; +} 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 0000000000..c3140608c5 --- /dev/null +++ b/browser/base/content/test/general/browser_bug832435.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 test() { + waitForExplicitFinish(); + ok(true, "Starting up"); + + gBrowser.selectedBrowser.focus(); + gURLBar.addEventListener( + "focus", + function () { + 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(); + }); + }, + { once: true } + ); + gURLBar.inputField.value = "javascript: var foo = '11111111'; "; + gURLBar.focus(); +} 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 0000000000..116f01b349 --- /dev/null +++ b/browser/base/content/test/general/browser_bug882977.js @@ -0,0 +1,33 @@ +"use strict"; + +/** + * Tests that the identity-box shows the chromeUI styling + * when viewing such a page in a new window. + */ +add_task(async function () { + let homepage = "about:preferences"; + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.startup.homepage", homepage], + ["browser.startup.page", 1], + ], + }); + + let win = OpenBrowserWindow(); + await BrowserTestUtils.firstBrowserLoaded(win, false); + + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, homepage, "Loaded the correct homepage"); + checkIdentityMode(win); + + await 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_bug963945.js b/browser/base/content/test/general/browser_bug963945.js new file mode 100644 index 0000000000..688d8b79ff --- /dev/null +++ b/browser/base/content/test/general/browser_bug963945.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/. */ + +/* + * This test ensures the about:addons tab is only + * opened one time when in private browsing. + */ + +add_task(async function test() { + let win = await BrowserTestUtils.openNewBrowserWindow({ private: true }); + + let tab = (win.gBrowser.selectedTab = BrowserTestUtils.addTab( + win.gBrowser, + "about:addons" + )); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await 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."); + + await BrowserTestUtils.closeWindow(win); +}); 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 0000000000..a4c823969f --- /dev/null +++ b/browser/base/content/test/general/browser_clipboard.js @@ -0,0 +1,290 @@ +// 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(async function () { + let tab = BrowserTestUtils.addTab(gBrowser); + let browser = gBrowser.getBrowserForTab(tab); + + gBrowser.selectedTab = tab; + + await promiseTabLoadEvent(tab, "data:text/html," + escape(testPage)); + await SimpleTest.promiseFocus(browser); + + function sendKey(key, code) { + return BrowserTestUtils.synthesizeKey( + key, + { code, accelKey: true }, + browser + ); + } + + // On windows, HTML clipboard includes extra data. + // The values are from widget/windows/nsDataObj.cpp. + const htmlPrefix = navigator.platform.includes("Win") + ? "<html><body>\n<!--StartFragment-->" + : ""; + const htmlPostfix = navigator.platform.includes("Win") + ? "<!--EndFragment-->\n</body>\n</html>" + : ""; + + await SpecialPowers.spawn(browser, [], () => { + var doc = content.document; + var main = doc.getElementById("main"); + main.focus(); + + // 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"); + }); + + // The data is empty as the selection was copied during the event default phase. + let copyEventPromise = BrowserTestUtils.waitForContentEvent( + browser, + "copy", + false, + event => { + return event.clipboardData.mozItemCount == 0; + } + ); + await SpecialPowers.spawn(browser, [], () => {}); + await sendKey("c"); + await copyEventPromise; + + let pastePromise = SpecialPowers.spawn( + browser, + [htmlPrefix, htmlPostfix], + (htmlPrefixChild, htmlPostfixChild) => { + let selection = content.document.getSelection(); + selection.modify("move", "right", "line"); + + return new Promise((resolve, reject) => { + content.addEventListener( + "paste", + event => { + 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"), + htmlPrefixChild + "t <b>Bold</b>" + htmlPostfixChild, + "text/html value" + ); + Assert.equal( + clipboardData.getData("text/plain"), + "t Bold", + "text/plain value" + ); + resolve(); + }, + { capture: true, once: true } + ); + }); + } + ); + + await SpecialPowers.spawn(browser, [], () => {}); + + await sendKey("v"); + await pastePromise; + + let copyPromise = SpecialPowers.spawn(browser, [], () => { + var main = content.document.getElementById("main"); + + Assert.equal( + main.innerHTML, + "Test <b>Bold</b> After Textt <b>Bold</b>", + "Copy and paste html" + ); + + let selection = content.document.getSelection(); + selection.modify("extend", "left", "word"); + selection.modify("extend", "left", "word"); + selection.modify("extend", "left", "character"); + + return new Promise((resolve, reject) => { + content.addEventListener( + "cut", + event => { + event.clipboardData.setData("text/plain", "Some text"); + event.clipboardData.setData("text/html", "<i>Italic</i> "); + selection.deleteFromDocument(); + event.preventDefault(); + resolve(); + }, + { capture: true, once: true } + ); + }); + }); + + await SpecialPowers.spawn(browser, [], () => {}); + + await sendKey("x"); + await copyPromise; + + pastePromise = SpecialPowers.spawn( + browser, + [htmlPrefix, htmlPostfix], + (htmlPrefixChild, htmlPostfixChild) => { + let selection = content.document.getSelection(); + selection.modify("move", "left", "line"); + + return new Promise((resolve, reject) => { + content.addEventListener( + "paste", + event => { + 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"), + htmlPrefixChild + "<i>Italic</i> " + htmlPostfixChild, + "text/html value 2" + ); + Assert.equal( + clipboardData.getData("text/plain"), + "Some text", + "text/plain value 2" + ); + resolve(); + }, + { capture: true, once: true } + ); + }); + } + ); + + await SpecialPowers.spawn(browser, [], () => {}); + + await sendKey("v"); + await pastePromise; + + await SpecialPowers.spawn(browser, [], () => { + var main = content.document.getElementById("main"); + 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 + ); + await contextMenuShown; + + document.getElementById("context-copyimage-contents").doCommand(); + + contextMenu.hidePopup(); + await promisePopupHidden(contextMenu); + + // Focus the content again + await SimpleTest.promiseFocus(browser); + + pastePromise = SpecialPowers.spawn( + browser, + [htmlPrefix, htmlPostfix], + (htmlPrefixChild, htmlPostfixChild) => { + var doc = content.document; + var main = doc.getElementById("main"); + main.focus(); + + return new Promise((resolve, reject) => { + content.addEventListener( + "paste", + event => { + let clipboardData = event.clipboardData; + + // DataTransfer doesn't support the image types yet, so only text/html + // will be present. + if ( + clipboardData.getData("text/html") !== + htmlPrefixChild + + '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' + + htmlPostfixChild + ) { + reject( + "Clipboard Data did not contain an image, was " + + clipboardData.getData("text/html") + ); + } + resolve(); + }, + { capture: true, once: true } + ); + }); + } + ); + + await SpecialPowers.spawn(browser, [], () => {}); + await sendKey("v"); + await pastePromise; + + // The new content should now include an image. + await SpecialPowers.spawn(browser, [], () => { + var main = content.document.getElementById("main"); + 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 0000000000..f034883ef2 --- /dev/null +++ b/browser/base/content/test/general/browser_clipboard_pastefile.js @@ -0,0 +1,133 @@ +/* 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 (real) files can be pasted into chrome/content. +// Pasting files should also hide all other data from content. + +function setClipboard(path) { + const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(path); + + const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(null); + trans.addDataFlavor("application/x-moz-file"); + trans.setTransferData("application/x-moz-file", file); + + trans.addDataFlavor("text/plain"); + const str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + str.data = "Alternate"; + trans.setTransferData("text/plain", str); + + // Write to clipboard. + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); +} + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["dom.events.dataTransfer.mozFile.enabled", true]], + }); + + // Create a temporary file that will be pasted. + const file = await IOUtils.createUniqueFile( + PathUtils.tempDir, + "test-file.txt", + 0o600 + ); + await IOUtils.writeUTF8(file, "Hello World!"); + + // Put the data directly onto the native clipboard to make sure + // it isn't handled internally in Gecko somehow. + setClipboard(file); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com/browser/browser/base/content/test/general/clipboard_pastefile.html" + ); + let browser = tab.linkedBrowser; + + let resultPromise = SpecialPowers.spawn(browser, [], function (arg) { + return new Promise(resolve => { + content.document.addEventListener("testresult", event => { + resolve(event.detail.result); + }); + }); + }); + + // Focus <input> in content + await SpecialPowers.spawn(browser, [], async function () { + content.document.getElementById("input").focus(); + }); + + // Paste file into <input> in content + await BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser); + + let result = await resultPromise; + is(result, PathUtils.filename(file), "Correctly pasted file in content"); + + var input = document.createElement("input"); + document.documentElement.appendChild(input); + input.focus(); + + await new Promise((resolve, reject) => { + input.addEventListener( + "paste", + function (event) { + 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.types.includes("application/x-moz-file"), + "application/x-moz-file exists in types" + ); + is(dt.types[2], "Files", "Last type should be 'Files'"); + 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" + ); + + ok( + dt.mozTypesAt(0).contains("application/x-moz-file"), + "application/x-moz-file exists in mozTypesAt" + ); + let mozFile = dt.mozGetDataAt("application/x-moz-file", 0); + + ok( + mozFile instanceof Ci.nsIFile, + "application/x-moz-file returned nsIFile with mozGetDataAt" + ); + + is( + mozFile.leafName, + PathUtils.filename(file), + "nsIFile has correct leafName" + ); + + resolve(); + }, + { capture: true, once: true } + ); + + EventUtils.synthesizeKey("v", { accelKey: true }); + }); + + input.remove(); + + BrowserTestUtils.removeTab(tab); + + await IOUtils.remove(file); +}); 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 0000000000..5f659d3351 --- /dev/null +++ b/browser/base/content/test/general/browser_contentAltClick.js @@ -0,0 +1,205 @@ +/* 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 the ClickHandler actor instead of browser.js::contentAreaClick() util. + */ +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", +}); + +function setup() { + Services.prefs.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="mathlink" xmlns="http://www.w3.org/1998/Math/MathML" 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><br>' + + '<span id="host"></span><script>document.getElementById("host").attachShadow({mode: "closed"}).appendChild(document.getElementById("commonlink").cloneNode(true));</script>' + + '<iframe id="frame" src="https://test2.example.com:443/browser/browser/base/content/test/general/file_with_link_to_http.html"></iframe>'; + + return BrowserTestUtils.openNewForegroundTab(gBrowser, testPage); +} + +async function clean_up() { + // Remove downloads. + let downloadList = await Downloads.getList(Downloads.ALL); + let downloads = await downloadList.getAll(); + for (let download of downloads) { + await downloadList.remove(download); + await download.finalize(true); + } + // Remove download history. + await PlacesUtils.history.clear(); + + Services.prefs.clearUserPref("browser.altClickSave"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +} + +add_task(async function test_alt_click() { + await setup(); + + let downloadList = await 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(aDownload) { + downloads.push(aDownload); + resolve(); + }, + }; + }); + await downloadList.addView(downloadView); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#commonlink", + { altKey: true }, + gBrowser.selectedBrowser + ); + + // Wait for all downloads to be added to the download list. + await finishedAllDownloads; + await downloadList.removeView(downloadView); + + is(downloads.length, 1, "1 downloads"); + is( + downloads[0].source.url, + "http://mochi.test/moz/", + "Downloaded #commonlink element" + ); + + await clean_up(); +}); + +add_task(async function test_alt_click_shadow_dom() { + await setup(); + + let downloadList = await 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(aDownload) { + downloads.push(aDownload); + resolve(); + }, + }; + }); + await downloadList.addView(downloadView); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#host", + { altKey: true }, + gBrowser.selectedBrowser + ); + + // Wait for all downloads to be added to the download list. + await finishedAllDownloads; + await downloadList.removeView(downloadView); + + is(downloads.length, 1, "1 downloads"); + is( + downloads[0].source.url, + "http://mochi.test/moz/", + "Downloaded #commonlink element in shadow DOM." + ); + + await clean_up(); +}); + +add_task(async function test_alt_click_on_xlinks() { + await setup(); + + let downloadList = await 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(aDownload) { + downloads.push(aDownload); + if (downloads.length == 2) { + resolve(); + } + }, + }; + }); + await downloadList.addView(downloadView); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#mathlink", + { altKey: true }, + gBrowser.selectedBrowser + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#svgxlink", + { altKey: true }, + gBrowser.selectedBrowser + ); + + // Wait for all downloads to be added to the download list. + await finishedAllDownloads; + await downloadList.removeView(downloadView); + + is(downloads.length, 2, "2 downloads"); + is( + downloads[0].source.url, + "http://mochi.test/moz/", + "Downloaded #mathlink element" + ); + is( + downloads[1].source.url, + "http://mochi.test/moz/", + "Downloaded #svgxlink element" + ); + + await clean_up(); +}); + +// Alt+Click a link in a frame from another domain as the outer document. +add_task(async function test_alt_click_in_frame() { + await setup(); + + let downloadList = await Downloads.getList(Downloads.ALL); + let downloads = []; + let downloadView; + // When the download has been attempted, resolve the promise. + let finishedAllDownloads = new Promise(resolve => { + downloadView = { + onDownloadAdded(aDownload) { + downloads.push(aDownload); + resolve(); + }, + }; + }); + + await downloadList.addView(downloadView); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#linkToExample", + { altKey: true }, + gBrowser.selectedBrowser.browsingContext.children[0] + ); + + // Wait for all downloads to be added to the download list. + await finishedAllDownloads; + await downloadList.removeView(downloadView); + + is(downloads.length, 1, "1 downloads"); + is( + downloads[0].source.url, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org/", + "Downloaded link in iframe." + ); + + await 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 0000000000..1a788e823f --- /dev/null +++ b/browser/base/content/test/general/browser_contentAreaClick.js @@ -0,0 +1,329 @@ +/* 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. + */ + +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); + +var gTests = [ + { + desc: "Simple left click", + setup() {}, + clean() {}, + event: {}, + targets: ["commonlink", "mathlink", "svgxlink", "maplink"], + expectedInvokedMethods: [], + preventDefault: false, + }, + + { + desc: "Ctrl/Cmd left click", + setup() {}, + clean() {}, + event: { ctrlKey: true, metaKey: true }, + targets: ["commonlink", "mathlink", "svgxlink", "maplink"], + expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], + preventDefault: true, + }, + + // The next test should just be like Alt click. + { + desc: "Shift+Alt left click", + setup() { + Services.prefs.setBoolPref("browser.altClickSave", true); + }, + clean() { + Services.prefs.clearUserPref("browser.altClickSave"); + }, + event: { shiftKey: true, altKey: true }, + targets: ["commonlink", "maplink"], + expectedInvokedMethods: ["gatherTextUnder", "saveURL"], + preventDefault: true, + }, + + { + desc: "Shift+Alt left click on XLinks", + setup() { + Services.prefs.setBoolPref("browser.altClickSave", true); + }, + clean() { + Services.prefs.clearUserPref("browser.altClickSave"); + }, + event: { shiftKey: true, altKey: true }, + targets: ["mathlink", "svgxlink"], + expectedInvokedMethods: ["saveURL"], + preventDefault: true, + }, + + { + desc: "Shift click", + setup() {}, + clean() {}, + event: { shiftKey: true }, + targets: ["commonlink", "mathlink", "svgxlink", "maplink"], + expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], + preventDefault: true, + }, + + { + desc: "Alt click", + setup() { + Services.prefs.setBoolPref("browser.altClickSave", true); + }, + clean() { + Services.prefs.clearUserPref("browser.altClickSave"); + }, + event: { altKey: true }, + targets: ["commonlink", "maplink"], + expectedInvokedMethods: ["gatherTextUnder", "saveURL"], + preventDefault: true, + }, + + { + desc: "Alt click on XLinks", + setup() { + Services.prefs.setBoolPref("browser.altClickSave", true); + }, + clean() { + Services.prefs.clearUserPref("browser.altClickSave"); + }, + event: { altKey: true }, + targets: ["mathlink", "svgxlink"], + expectedInvokedMethods: ["saveURL"], + preventDefault: true, + }, + + { + desc: "Panel click", + setup() {}, + clean() {}, + event: {}, + targets: ["panellink"], + expectedInvokedMethods: ["urlSecurityCheck", "loadURI"], + preventDefault: true, + }, + + { + desc: "Simple middle click opentab", + setup() {}, + clean() {}, + event: { button: 1 }, + wantedEvent: "auxclick", + targets: ["commonlink", "mathlink", "svgxlink", "maplink"], + expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], + preventDefault: true, + }, + + { + desc: "Simple middle click openwin", + setup() { + Services.prefs.setBoolPref("browser.tabs.opentabfor.middleclick", false); + }, + clean() { + Services.prefs.clearUserPref("browser.tabs.opentabfor.middleclick"); + }, + event: { button: 1 }, + wantedEvent: "auxclick", + targets: ["commonlink", "mathlink", "svgxlink", "maplink"], + expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], + preventDefault: true, + }, + + { + desc: "Middle mouse paste", + setup() { + Services.prefs.setBoolPref("middlemouse.contentLoadURL", true); + Services.prefs.setBoolPref("general.autoScroll", false); + }, + clean() { + Services.prefs.clearUserPref("middlemouse.contentLoadURL"); + Services.prefs.clearUserPref("general.autoScroll"); + }, + event: { button: 1 }, + wantedEvent: "auxclick", + 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", +]; + +// Returns the target object for the replaced method. +function getStub(replacedMethod) { + let targetObj = + replacedMethod == "getShortcutOrURIAndPostData" ? UrlbarUtils : gTestWin; + return targetObj[replacedMethod]; +} + +// Reference to the new window. +var gTestWin = null; + +// The test currently running. +var gCurrentTest = null; + +function test() { + waitForExplicitFinish(); + + registerCleanupFunction(function () { + sinon.restore(); + }); + + 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(event) { + if (event.type == "click" && event.button != 0) { + return; + } + let linkId = event.target.id || event.target.localName; + let wantedEvent = gCurrentTest.wantedEvent || "click"; + is( + event.type, + wantedEvent, + `${gCurrentTest.desc}:Handler received a ${wantedEvent} 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. + for (let expectedMethod of gCurrentTest.expectedInvokedMethods) { + ok( + getStub(expectedMethod).called, + `${gCurrentTest.desc}:${expectedMethod} should have been invoked` + ); + } + + for (let method of gReplacedMethods) { + if ( + getStub(method).called && + !gCurrentTest.expectedInvokedMethods.includes(method) + ) { + ok(false, `Should have not called ${method}`); + } + } + + event.preventDefault(); + event.stopPropagation(); + + executeSoon(runNextTest); + }, +}; + +function setupTestBrowserWindow() { + // Steal click events and don't propagate them. + gTestWin.addEventListener("click", gClickHandler, true); + gTestWin.addEventListener("auxclick", gClickHandler, true); + + // Replace methods. + gReplacedMethods.forEach(function (methodName) { + let targetObj = + methodName == "getShortcutOrURIAndPostData" ? UrlbarUtils : gTestWin; + sinon.stub(targetObj, methodName).returnsArg(0); + }); + + // 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="mathlink" xmlns="http://www.w3.org/1998/Math/MathML" 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) { + info(gCurrentTest.desc + ": cleaning up..."); + gCurrentTest.clean(); + + if (gTests.length) { + gCurrentTest = gTests.shift(); + gCurrentTest.setup(); + } else { + finishTest(); + return; + } + } + + // Move to next target. + sinon.resetHistory(); + let target = gCurrentTest.targets.shift(); + + info(gCurrentTest.desc + ": testing " + target); + + // Fire (aux)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); + gTestWin.removeEventListener("auxclick", gClickHandler, true); + gTestWin.close(); + finish(); +} 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 0000000000..7c4a7b6c23 --- /dev/null +++ b/browser/base/content/test/general/browser_ctrlTab.js @@ -0,0 +1,464 @@ +/* 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(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["browser.ctrlTab.sortByRecentlyUsed", true]], + }); + + BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.addTab(gBrowser); + + // While doing this test, we should make sure the selected tab in the tab + // preview is not changed by mouse events. That may happen after closing + // the selected tab with ctrl+W. Disable all mouse events to prevent it. + for (let node of ctrlTab.previews) { + node.style.pointerEvents = "none"; + } + registerCleanupFunction(function () { + for (let node of ctrlTab.previews) { + try { + node.style.removeProperty("pointer-events"); + } catch (e) {} + } + }); + + checkTabs(4); + + await ctrlTabTest([2], 1, 0); + await ctrlTabTest([2, 3, 1], 2, 2); + await ctrlTabTest([], 4, 2); + + { + let selectedIndex = gBrowser.tabContainer.selectedIndex; + await pressCtrlTab(); + await pressCtrlTab(true); + await releaseCtrl(); + is( + gBrowser.tabContainer.selectedIndex, + selectedIndex, + "Ctrl+Tab -> Ctrl+Shift+Tab keeps the selected tab" + ); + } + + { + info("test for bug 445369"); + let tabs = gBrowser.tabs.length; + await pressCtrlTab(); + await synthesizeCtrlW(); + is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes one tab"); + await releaseCtrl(); + } + + { + info("test for bug 667314"); + let tabs = gBrowser.tabs.length; + await pressCtrlTab(); + await pressCtrlTab(true); + await synthesizeCtrlW(); + is( + gBrowser.tabs.length, + tabs - 1, + "Ctrl+Tab -> Ctrl+W removes the selected tab" + ); + await releaseCtrl(); + } + + BrowserTestUtils.addTab(gBrowser); + checkTabs(3); + await ctrlTabTest([2, 1, 0], 7, 1); + + { + info("test for bug 1292049"); + let tabToClose = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:buildconfig" + ); + checkTabs(4); + selectTabs([0, 1, 2, 3]); + + let promise = BrowserTestUtils.waitForSessionStoreUpdate(tabToClose); + BrowserTestUtils.removeTab(tabToClose); + await promise; + checkTabs(3); + undoCloseTab(); + checkTabs(4); + is( + gBrowser.tabContainer.selectedIndex, + 3, + "tab is selected after closing and restoring it" + ); + + await ctrlTabTest([], 1, 2); + } + + { + info("test for bug 445369"); + checkTabs(4); + selectTabs([1, 2, 0]); + + let selectedTab = gBrowser.selectedTab; + let tabToRemove = gBrowser.tabs[1]; + + await pressCtrlTab(); + await pressCtrlTab(); + await synthesizeCtrlW(); + ok( + !tabToRemove.parentNode, + "Ctrl+Tab*2 -> Ctrl+W removes the second most recently selected tab" + ); + + await pressCtrlTab(true); + await pressCtrlTab(true); + await releaseCtrl(); + ok( + selectedTab.selected, + "Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab" + ); + } + gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + checkTabs(2); + + await ctrlTabTest([1], 1, 0); + + gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + checkTabs(1); + + { + info("test for bug 445768"); + let focusedWindow = document.commandDispatcher.focusedWindow; + let eventConsumed = true; + let detectKeyEvent = function (event) { + eventConsumed = event.defaultPrevented; + }; + document.addEventListener("keypress", detectKeyEvent); + await pressCtrlTab(); + document.removeEventListener("keypress", detectKeyEvent); + 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" + ); + } + + // eslint-disable-next-line no-lone-blocks + { + info("Bug 1731050: test hidden tabs"); + checkTabs(1); + await BrowserTestUtils.addTab(gBrowser); + await BrowserTestUtils.addTab(gBrowser); + await BrowserTestUtils.addTab(gBrowser); + await BrowserTestUtils.addTab(gBrowser); + FirefoxViewHandler.tab = await BrowserTestUtils.addTab(gBrowser); + + gBrowser.hideTab(FirefoxViewHandler.tab); + FirefoxViewHandler.openTab(); + selectTabs([1, 2, 3, 4, 3]); + gBrowser.hideTab(gBrowser.tabs[4]); + selectTabs([2]); + gBrowser.hideTab(gBrowser.tabs[3]); + + is(gBrowser.tabs[5].hidden, true, "Tab at index 5 is hidden"); + is(gBrowser.tabs[4].hidden, true, "Tab at index 4 is hidden"); + is(gBrowser.tabs[3].hidden, true, "Tab at index 3 is hidden"); + is(gBrowser.tabs[2].hidden, false, "Tab at index 2 is still shown"); + is(gBrowser.tabs[1].hidden, false, "Tab at index 1 is still shown"); + is(gBrowser.tabs[0].hidden, false, "Tab at index 0 is still shown"); + + await ctrlTabTest([], 1, 1); + await ctrlTabTest([], 2, 0); + gBrowser.showTab(gBrowser.tabs[4]); + await ctrlTabTest([2], 3, 4); + await ctrlTabTest([], 4, 4); + gBrowser.showTab(gBrowser.tabs[3]); + await ctrlTabTest([], 4, 3); + await ctrlTabTest([], 6, 4); + FirefoxViewHandler.openTab(); + // Fx View tab should be visible in the panel while selected. + await ctrlTabTest([], 5, 1); + // Fx View tab should no longer be visible. + await ctrlTabTest([], 1, 4); + + for (let i = 5; i > 0; i--) { + await BrowserTestUtils.removeTab(gBrowser.tabs[i]); + } + FirefoxViewHandler.tab = null; + info("End hidden tabs test"); + } + + { + info("Bug 1293692: Test asynchronous tab previews"); + + checkTabs(1); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.pagethumbnails.capturing_disabled", false]], + }); + + await BrowserTestUtils.addTab(gBrowser); + await BrowserTestUtils.addTab(gBrowser); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + `${getRootDirectory(gTestPath)}dummy_page.html` + ); + + info("Pressing Ctrl+Tab to open the panel"); + ok(canOpen(), "Ctrl+Tab can open the preview panel"); + let panelShown = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown"); + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true }); + await TestUtils.waitForTick(); + + let observedPreview = ctrlTab.previews[0]; + is(observedPreview._tab, tab, "The observed preview is for the new tab"); + ok( + !observedPreview._canvas.firstElementChild, + "The preview <canvas> does not exist yet" + ); + + let emptyCanvas = PageThumbs.createCanvas(window); + let emptyImageData = emptyCanvas + .getContext("2d") + .getImageData(0, 0, ctrlTab.canvasWidth, ctrlTab.canvasHeight) + .data.slice(0, 15) + .toString(); + + info("Waiting for the preview <canvas> to be loaded"); + await BrowserTestUtils.waitForMutationCondition( + observedPreview._canvas, + { + childList: true, + attributes: true, + subtree: true, + }, + () => + HTMLCanvasElement.isInstance(observedPreview._canvas.firstElementChild) + ); + + // Ensure the image is not blank (see bug 1293692). The canvas shouldn't be + // rendered at all until it has image data, but this will allow us to catch + // any regressions in the future. + await BrowserTestUtils.waitForCondition( + () => + emptyImageData !== + observedPreview._canvas.firstElementChild + .getContext("2d") + .getImageData(0, 0, ctrlTab.canvasWidth, ctrlTab.canvasHeight) + .data.slice(0, 15) + .toString(), + "The preview <canvas> should be filled with a thumbnail" + ); + + // Wait for the panel to be shown. + await panelShown; + ok(isOpen(), "The preview panel is open"); + + // Keep the same tab selected. + await pressCtrlTab(true); + await releaseCtrl(); + + // The next time the panel is open, our preview should now be an <img>, the + // thumbnail that was previously drawn in a <canvas> having been cached and + // now being immediately available for reuse. + info("Pressing Ctrl+Tab to open the panel again"); + let imgExists = BrowserTestUtils.waitForMutationCondition( + observedPreview._canvas, + { + childList: true, + attributes: true, + subtree: true, + }, + () => { + let img = observedPreview._canvas.firstElementChild; + return ( + img && + HTMLImageElement.isInstance(img) && + img.src && + img.complete && + img.naturalWidth + ); + } + ); + let panelShownAgain = BrowserTestUtils.waitForEvent( + ctrlTab.panel, + "popupshown" + ); + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true }); + + info("Waiting for the preview <img> to be loaded"); + await imgExists; + ok( + true, + `The preview image is an <img> with src="${observedPreview._canvas.firstElementChild.src}"` + ); + await panelShownAgain; + await releaseCtrl(); + + for (let i = gBrowser.tabs.length - 1; i > 0; i--) { + await BrowserTestUtils.removeTab(gBrowser.tabs[i]); + } + checkTabs(1); + } + + /* private utility functions */ + + /** + * @return the number of times (Shift+)Ctrl+Tab was pressed + */ + async function pressCtrlTab(aShiftKey = false) { + let promise; + if (!isOpen() && canOpen()) { + ok( + !aShiftKey, + "Shouldn't attempt to open the panel by pressing Shift+Ctrl+Tab" + ); + info("Pressing Ctrl+Tab to open the panel"); + promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown"); + } else { + info( + `Pressing ${aShiftKey ? "Shift+" : ""}Ctrl+Tab while the panel is open` + ); + promise = BrowserTestUtils.waitForEvent(document, "keyup"); + } + EventUtils.synthesizeKey("VK_TAB", { + ctrlKey: true, + shiftKey: !!aShiftKey, + }); + await promise; + if (document.activeElement == ctrlTab.showAllButton) { + info("Repeating keypress to skip over the 'List all tabs' button"); + return 1 + (await pressCtrlTab(aShiftKey)); + } + return 1; + } + + async function releaseCtrl() { + let promise; + if (isOpen()) { + promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden"); + } else { + promise = BrowserTestUtils.waitForEvent(document, "keyup"); + } + EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }); + await promise; + } + + async function synthesizeCtrlW() { + let promise = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabClose" + ); + EventUtils.synthesizeKey("w", { ctrlKey: true }); + await promise; + } + + function isOpen() { + return ctrlTab.isOpen; + } + + function canOpen() { + return ( + Services.prefs.getBoolPref("browser.ctrlTab.sortByRecentlyUsed") && + 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]; + }); + } + + async function ctrlTabTest(tabsToSelect, tabTimes, expectedIndex) { + selectTabs(tabsToSelect); + + var indexStart = gBrowser.tabContainer.selectedIndex; + var tabCount = gBrowser.visibleTabs.length; + var normalized = tabTimes % tabCount; + var where = + normalized == 1 + ? "back to the previously selected tab" + : normalized + " tabs back in most-recently-selected order"; + + // Add keyup listener to all content documents. + await Promise.all( + gBrowser.tabs.map(tab => + SpecialPowers.spawn(tab.linkedBrowser, [], () => { + if (!content.windowGlobalChild?.isInProcess) { + content.window.addEventListener("keyup", () => { + content.window._ctrlTabTestKeyupHappend = true; + }); + } + }) + ) + ); + + let numTimesPressed = 0; + for (let i = 0; i < tabTimes; i++) { + numTimesPressed += await pressCtrlTab(); + + if (tabCount > 2) { + is( + gBrowser.tabContainer.selectedIndex, + indexStart, + "Selected tab doesn't change while tabbing" + ); + } + } + + if (tabCount > 2) { + ok( + isOpen(), + "With " + tabCount + " visible tabs, Ctrl+Tab opens the preview panel" + ); + + await releaseCtrl(); + + ok(!isOpen(), "Releasing Ctrl closes the preview panel"); + } else { + ok( + !isOpen(), + "With " + + tabCount + + " visible tabs, Ctrl+Tab doesn't open the preview panel" + ); + } + + is( + gBrowser.tabContainer.selectedIndex, + expectedIndex, + "With " + + tabCount + + " visible tabs and tab " + + indexStart + + " selected, Ctrl+Tab*" + + numTimesPressed + + " goes " + + where + ); + + const keyupEvents = await Promise.all( + gBrowser.tabs.map(tab => + SpecialPowers.spawn( + tab.linkedBrowser, + [], + () => !!content.window._ctrlTabTestKeyupHappend + ) + ) + ); + ok( + keyupEvents.every(isKeyupHappned => !isKeyupHappned), + "Content document doesn't capture Keyup event during cycling tabs" + ); + } +}); 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 0000000000..eb2ec5ee37 --- /dev/null +++ b/browser/base/content/test/general/browser_datachoices_notification.js @@ -0,0 +1,287 @@ +/* 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"; + +var { Preferences } = ChromeUtils.importESModule( + "resource://gre/modules/Preferences.sys.mjs" +); +var { TelemetryReportingPolicy } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryReportingPolicy.sys.mjs" +); + +const PREF_BRANCH = "datareporting.policy."; +const PREF_FIRST_RUN = "toolkit.telemetry.reportingpolicy.firstRun"; +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 PREF_TELEMETRY_LOG_LEVEL = "toolkit.telemetry.log.level"; + +const TEST_POLICY_VERSION = 37; + +function fakeShowPolicyTimeout(set, clear) { + let reportingPolicy = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryReportingPolicy.sys.mjs" + ).Policy; + reportingPolicy.setShowInfobarTimeout = set; + reportingPolicy.clearShowInfobarTimeout = clear; +} + +function sendSessionRestoredNotification() { + let reportingPolicy = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryReportingPolicy.sys.mjs" + ).Policy; + + reportingPolicy.fakeSessionRestoreNotification(); +} + +/** + * 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.stack.addEventListener( + "AlertActive", + function () { + deferred.resolve(); + }, + { once: true } + ); + 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 = async function (aNotification) { + // Check that the button on the data choices infobar does the right thing. + let buttons = aNotification.buttonContainer.getElementsByTagName("button"); + Assert.equal( + buttons.length, + 1, + "There is 1 button in the data reporting notification." + ); + let button = buttons[0]; + + // Click on the button. + button.click(); + + // Wait for the preferences panel to open. + await promiseNextTick(); +}; + +add_setup(async function () { + const isFirstRun = Preferences.get(PREF_FIRST_RUN, true); + 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_FIRST_RUN, isFirstRun); + Preferences.set(PREF_BYPASS_NOTIFICATION, bypassNotification); + Preferences.set(PREF_CURRENT_POLICY_VERSION, currentPolicyVersion); + Preferences.reset(PREF_TELEMETRY_LOG_LEVEL); + + 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); + // Ensure this isn't the first run, because then we open the first run page. + Preferences.set(PREF_FIRST_RUN, false); + TelemetryReportingPolicy.testUpdateFirstRun(); +}); + +function clearAcceptedPolicy() { + // Reset the accepted policy. + Preferences.reset(PREF_ACCEPTED_POLICY_VERSION); + Preferences.reset(PREF_ACCEPTED_POLICY_DATE); +} + +function assertCoherentInitialState() { + // 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." + ); +} + +add_task(async function test_single_window() { + clearAcceptedPolicy(); + + // Close all the notifications, then try to trigger the data choices infobar. + await closeAllNotifications(); + + assertCoherentInitialState(); + + let alertShownPromise = promiseWaitForAlertActive(gNotificationBox); + Assert.ok( + !TelemetryReportingPolicy.canUpload(), + "User should not be allowed to upload." + ); + + // Wait for the infobar to be displayed. + triggerInfoBar(10 * 1000); + await alertShownPromise; + + Assert.equal( + gNotificationBox.allNotifications.length, + 1, + "Notification Displayed." + ); + Assert.ok( + TelemetryReportingPolicy.canUpload(), + "User should be allowed to upload now." + ); + + await promiseNextTick(); + let promiseClosed = promiseWaitForNotificationClose( + gNotificationBox.currentNotification + ); + await checkInfobarButton(gNotificationBox.currentNotification); + await promiseClosed; + + Assert.equal( + gNotificationBox.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." + ); +}); + +/* See bug 1571932 +add_task(async function test_multiple_windows() { + clearAcceptedPolicy(); + + // Close all the notifications, then try to trigger the data choices infobar. + await closeAllNotifications(); + + // Ensure we see the notification on all windows and that action on one window + // results in dismiss on every window. + let otherWindow = await BrowserTestUtils.openNewBrowserWindow(); + + Assert.ok( + otherWindow.gNotificationBox, + "2nd window has a global notification box." + ); + + assertCoherentInitialState(); + + let showAlertPromises = [ + promiseWaitForAlertActive(gNotificationBox), + promiseWaitForAlertActive(otherWindow.gNotificationBox), + ]; + + Assert.ok( + !TelemetryReportingPolicy.canUpload(), + "User should not be allowed to upload." + ); + + // Wait for the infobars. + triggerInfoBar(10 * 1000); + await Promise.all(showAlertPromises); + + // Both notification were displayed. Close one and check that both gets closed. + let closeAlertPromises = [ + promiseWaitForNotificationClose(gNotificationBox.currentNotification), + promiseWaitForNotificationClose( + otherWindow.gNotificationBox.currentNotification + ), + ]; + gNotificationBox.currentNotification.close(); + await Promise.all(closeAlertPromises); + + // Close the second window we opened. + await 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_documentnavigation.js b/browser/base/content/test/general/browser_documentnavigation.js new file mode 100644 index 0000000000..8a4fd2ca6b --- /dev/null +++ b/browser/base/content/test/general/browser_documentnavigation.js @@ -0,0 +1,493 @@ +/* + * 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; + +async function expectFocusOnF6( + backward, + expectedDocument, + expectedElement, + onContent, + desc +) { + if (onContent) { + let success = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [expectedElement], + async function (expectedElementId) { + content.lastResult = ""; + let contentExpectedElement = + content.document.getElementById(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(expectedElementId)) { + contentExpectedElement = content.frames[f].document; + break; + } + } + } else if (contentExpectedElement.localName == "html") { + contentExpectedElement = contentExpectedElement.ownerDocument; + } + + if (!contentExpectedElement) { + return null; + } + + contentExpectedElement.addEventListener( + "focus", + function () { + let details = + Services.focus.focusedWindow.document.documentElement.id; + if (Services.focus.focusedElement) { + details += "," + Services.focus.focusedElement.id; + } + + // Assign the result to a temporary place, to be used + // by the next spawn call. + content.lastResult = details; + }, + { capture: true, once: true } + ); + + return !!contentExpectedElement; + } + ); + + ok(success, "expected element " + expectedElement + " was found"); + + EventUtils.synthesizeKey("VK_F6", { shiftKey: backward }); + + let expected = expectedDocument; + if (!expectedElement.startsWith("html")) { + expected += "," + expectedElement; + } + + let result = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + async () => { + await ContentTaskUtils.waitForCondition(() => content.lastResult); + return content.lastResult; + } + ); + is(result, expected, desc + " child focus matches"); + } else { + let focusPromise = BrowserTestUtils.waitForEvent(window, "focus", true); + EventUtils.synthesizeKey("VK_F6", { shiftKey: backward }); + await 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 (wanted: " + + expectedElement.id + + " got: " + + fm.focusedElement.id + + ")" + ); +} + +// Load a page and navigate between it and the chrome window. +add_task(async function () { + let page1Promise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + testPage1 + ); + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPage1); + await page1Promise; + + // When the urlbar is focused, pressing F6 should focus the root of the content page. + gURLBar.focus(); + await expectFocusOnF6( + false, + "html1", + "html1", + true, + "basic focus content page" + ); + + // When the content is focused, pressing F6 should focus the urlbar. + await 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. + await expectFocusOnF6( + false, + "html1", + "html1", + true, + "basic focus content page with button focused" + ); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + return content.document.getElementById("button1").focus(); + }); + + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "basic focus content page with button focused urlbar" + ); + + // The document root should be focused, not the button + await expectFocusOnF6( + false, + "html1", + "html1", + true, + "basic focus again content page with button focused" + ); + + // Check to ensure that the root element is focused + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async 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(async function () { + await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2); + + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "basic focus content page and second tab urlbar" + ); + await expectFocusOnF6( + false, + "html2", + "html2", + true, + "basic focus content page with second tab" + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Shift+F6 should navigate backwards. There's only one document here so the effect +// is the same. +add_task(async function () { + gURLBar.focus(); + await expectFocusOnF6( + true, + "html1", + "html1", + true, + "back focus content page" + ); + await 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(async function () { + let sidebar = document.getElementById("sidebar"); + + let loadPromise = BrowserTestUtils.waitForEvent(sidebar, "load", true); + SidebarUI.toggle("viewBookmarksSidebar"); + await loadPromise; + + gURLBar.focus(); + await expectFocusOnF6( + false, + "bookmarksPanel", + sidebar.contentDocument.getElementById("search-box").inputField, + false, + "focus with sidebar open sidebar" + ); + await expectFocusOnF6( + false, + "html1", + "html1", + true, + "focus with sidebar open content" + ); + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "focus with sidebar urlbar" + ); + + // Now go backwards + await expectFocusOnF6( + true, + "html1", + "html1", + true, + "back focus with sidebar open content" + ); + await expectFocusOnF6( + true, + "bookmarksPanel", + sidebar.contentDocument.getElementById("search-box").inputField, + false, + "back focus with sidebar open sidebar" + ); + await expectFocusOnF6( + true, + "main-window", + gURLBar.inputField, + false, + "back focus with sidebar urlbar" + ); + + SidebarUI.toggle("viewBookmarksSidebar"); +}); + +// Navigate when the downloads panel is open +add_task(async function test_download_focus() { + await pushPrefs( + ["accessibility.tabfocus", 7], + ["browser.download.autohideButton", false], + ["security.dialog_enable_delay", 0] + ); + await promiseButtonShown("downloads-button"); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + document, + "popupshown", + true + ); + EventUtils.synthesizeMouseAtCenter( + document.getElementById("downloads-button"), + {} + ); + await popupShownPromise; + + gURLBar.focus(); + await expectFocusOnF6( + false, + "main-window", + document.getElementById("downloadsHistory"), + false, + "focus with downloads panel open panel" + ); + await expectFocusOnF6( + false, + "html1", + "html1", + true, + "focus with downloads panel open" + ); + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "focus downloads panel open urlbar" + ); + + // Now go backwards + await expectFocusOnF6( + true, + "html1", + "html1", + true, + "back focus with downloads panel open" + ); + await expectFocusOnF6( + true, + "main-window", + document.getElementById("downloadsHistory"), + false, + "back focus with downloads panel open" + ); + await 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(); + await popupHiddenPromise; +}); + +// Navigation with a contenteditable body +add_task(async function () { + await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3); + + // The body should be focused when it is editable, not the root. + gURLBar.focus(); + await expectFocusOnF6( + false, + "html3", + "body3", + true, + "focus with contenteditable body" + ); + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "focus with contenteditable body urlbar" + ); + + // Now go backwards + + await expectFocusOnF6( + false, + "html3", + "body3", + true, + "back focus with contenteditable body" + ); + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "back focus with contenteditable body urlbar" + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Navigation with a frameset loaded +add_task(async function () { + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888/browser/browser/base/content/test/general/file_documentnavigation_frameset.html" + ); + + gURLBar.focus(); + await expectFocusOnF6( + false, + "htmlframe1", + "htmlframe1", + true, + "focus on frameset frame 0" + ); + await expectFocusOnF6( + false, + "htmlframe2", + "htmlframe2", + true, + "focus on frameset frame 1" + ); + await expectFocusOnF6( + false, + "htmlframe3", + "htmlframe3", + true, + "focus on frameset frame 2" + ); + await expectFocusOnF6( + false, + "htmlframe4", + "htmlframe4", + true, + "focus on frameset frame 3" + ); + await expectFocusOnF6( + false, + "main-window", + gURLBar.inputField, + false, + "focus on frameset frame urlbar" + ); + + await expectFocusOnF6( + true, + "htmlframe4", + "htmlframe4", + true, + "back focus on frameset frame 3" + ); + await expectFocusOnF6( + true, + "htmlframe3", + "htmlframe3", + true, + "back focus on frameset frame 2" + ); + await expectFocusOnF6( + true, + "htmlframe2", + "htmlframe2", + true, + "back focus on frameset frame 1" + ); + await expectFocusOnF6( + true, + "htmlframe1", + "htmlframe1", + true, + "back focus on frameset frame 0" + ); + await expectFocusOnF6( + true, + "main-window", + gURLBar.inputField, + false, + "back focus on frameset frame urlbar" + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// XXXndeakin add tests for browsers inside of panels + +function promiseButtonShown(id) { + let dwu = window.windowUtils; + return TestUtils.waitForCondition(() => { + let target = document.getElementById(id); + let bounds = dwu.getBoundsWithoutFlushing(target); + return bounds.width > 0 && bounds.height > 0; + }, `Waiting for button ${id} to have non-0 size`); +} 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 0000000000..c96fa6cf7b --- /dev/null +++ b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js @@ -0,0 +1,237 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +"use strict"; + +// This test tends to trigger a race in the fullscreen time telemetry, +// where the fullscreen enter and fullscreen exit events (which use the +// same histogram ID) overlap. That causes TelemetryStopwatch to log an +// error. +SimpleTest.ignoreAllUncaughtExceptions(true); + +function listenOneEvent(aEvent, aListener) { + function listener(evt) { + removeEventListener(aEvent, listener); + aListener(evt); + } + addEventListener(aEvent, listener); +} + +function queryFullscreenState(browser) { + return SpecialPowers.spawn(browser, [], () => { + return { + inDOMFullscreen: !!content.document.fullscreenElement, + inFullscreen: content.fullScreen, + }; + }); +} + +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 waitForDocActivated(aBrowser) { + return SpecialPowers.spawn(aBrowser, [], () => { + return ContentTaskUtils.waitForCondition( + () => content.browsingContext.isActive && content.document.hasFocus() + ); + }); +} + +function waitForFullscreenChanges(aBrowser, aFlags) { + return new Promise(resolve => { + let fullscreenData = null; + let sizemodeChanged = false; + function tryResolve() { + if ( + (!(aFlags & FS_CHANGE_DOM) || fullscreenData) && + (!(aFlags & FS_CHANGE_SIZE) || sizemodeChanged) + ) { + // In the platforms that support reporting occlusion state (e.g. Mac), + // enter/exit fullscreen mode will trigger docshell being set to + // non-activate and then set to activate back again. + // For those platform, we should wait until the docshell has been + // activated again, otherwise, the fullscreen request might be denied. + waitForDocActivated(aBrowser).then(() => { + if (!fullscreenData) { + queryFullscreenState(aBrowser).then(resolve); + } else { + resolve(fullscreenData); + } + }); + } + } + if (aFlags & FS_CHANGE_SIZE) { + listenOneEvent("sizemodechange", () => { + sizemodeChanged = true; + tryResolve(); + }); + } + if (aFlags & FS_CHANGE_DOM) { + BrowserTestUtils.waitForContentEvent(aBrowser, "fullscreenchange").then( + async () => { + fullscreenData = await queryFullscreenState(aBrowser); + tryResolve(); + } + ); + } + }); +} + +var gTests = [ + { + desc: "document method", + affectsFullscreenMode: false, + exitFunc: browser => { + SpecialPowers.spawn(browser, [], () => { + content.document.exitFullscreen(); + }); + }, + }, + { + desc: "escape key", + affectsFullscreenMode: false, + exitFunc: () => { + executeSoon(() => EventUtils.synthesizeKey("KEY_Escape")); + }, + }, + { + desc: "F11 key", + affectsFullscreenMode: true, + exitFunc() { + executeSoon(() => EventUtils.synthesizeKey("KEY_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 = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org/browser/browser/" + + "base/content/test/general/dummy_page.html"; + +add_task(async function () { + await pushPrefs( + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"] + ); + + registerCleanupFunction(async function () { + if (window.fullScreen) { + let fullscreenPromise = waitForFullscreenChanges( + gBrowser.selectedBrowser, + FS_CHANGE_SIZE + ); + executeSoon(() => BrowserFullScreen()); + await fullscreenPromise; + } + }); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: kPage, + }); + let browser = tab.linkedBrowser; + + // As requestFullscreen checks the active state of the docshell, + // wait for the document to be activated, just to be sure that + // the fullscreen request won't be denied. + await waitForDocActivated(browser); + + for (let test of gTests) { + let contentStates; + info("Testing exit DOM fullscreen via " + test.desc); + + contentStates = await queryFullscreenState(browser); + checkState({ inDOMFullscreen: false, inFullscreen: false }, contentStates); + + /* DOM fullscreen without fullscreen mode */ + + info("> Enter DOM fullscreen"); + let fullscreenPromise = waitForFullscreenChanges(browser, FS_CHANGE_BOTH); + await SpecialPowers.spawn(browser, [], () => { + content.document.body.requestFullscreen(); + }); + contentStates = await fullscreenPromise; + checkState({ inDOMFullscreen: true, inFullscreen: true }, contentStates); + + info("> Exit DOM fullscreen"); + fullscreenPromise = waitForFullscreenChanges(browser, FS_CHANGE_BOTH); + test.exitFunc(browser); + contentStates = await fullscreenPromise; + 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. + fullscreenPromise = waitForFullscreenChanges(browser, FS_CHANGE_SIZE); + executeSoon(() => BrowserFullScreen()); + contentStates = await fullscreenPromise; + checkState({ inDOMFullscreen: false, inFullscreen: true }, contentStates); + + info("> Enter DOM fullscreen in fullscreen mode"); + fullscreenPromise = waitForFullscreenChanges(browser, FS_CHANGE_DOM); + await SpecialPowers.spawn(browser, [], () => { + content.document.body.requestFullscreen(); + }); + contentStates = await fullscreenPromise; + checkState({ inDOMFullscreen: true, inFullscreen: true }, contentStates); + + info("> Exit DOM fullscreen in fullscreen mode"); + fullscreenPromise = waitForFullscreenChanges( + browser, + test.affectsFullscreenMode ? FS_CHANGE_BOTH : FS_CHANGE_DOM + ); + test.exitFunc(browser); + contentStates = await fullscreenPromise; + checkState( + { + inDOMFullscreen: false, + inFullscreen: !test.affectsFullscreenMode, + }, + contentStates + ); + + /* Cleanup */ + + // Exit fullscreen mode if we are still in + if (window.fullScreen) { + info("> Cleanup"); + fullscreenPromise = waitForFullscreenChanges(browser, FS_CHANGE_SIZE); + executeSoon(() => BrowserFullScreen()); + await fullscreenPromise; + } + } + + BrowserTestUtils.removeTab(tab); +}); 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 0000000000..554aeb8077 --- /dev/null +++ b/browser/base/content/test/general/browser_double_close_tab.js @@ -0,0 +1,120 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; +const TEST_PAGE = + "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html"; +var testTab; + +const CONTENT_PROMPT_SUBDIALOG = Services.prefs.getBoolPref( + "prompts.contentPromptSubDialog", + false +); + +function waitForDialog(callback) { + function onDialogLoaded(nodeOrDialogWindow) { + let node = CONTENT_PROMPT_SUBDIALOG + ? nodeOrDialogWindow.document.querySelector("dialog") + : nodeOrDialogWindow; + Services.obs.removeObserver(onDialogLoaded, "tabmodal-dialog-loaded"); + Services.obs.removeObserver(onDialogLoaded, "common-dialog-loaded"); + // Allow dialog's onLoad call to run to completion + Promise.resolve().then(() => callback(node)); + } + + // Listen for the dialog being created + Services.obs.addObserver(onDialogLoaded, "tabmodal-dialog-loaded"); + Services.obs.addObserver(onDialogLoaded, "common-dialog-loaded"); +} + +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 }); + + if (CONTENT_PROMPT_SUBDIALOG) { + node.ownerGlobal.addEventListener("unload", done); + } + + let failureTimeout = setTimeout(function () { + ok(false, "Dialog should have been destroyed"); + done(); + }, 10000); + + function done() { + clearTimeout(failureTimeout); + observer.disconnect(); + observer = null; + + if (CONTENT_PROMPT_SUBDIALOG) { + node.ownerGlobal.removeEventListener("unload", done); + SimpleTest.executeSoon(callback); + } else { + callback(); + } + } +} + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["dom.require_user_interaction_for_beforeunload", false]], + }); + + testTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, 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. + await new Promise(resolveOuter => { + waitForDialog(dialogNode => { + waitForDialogDestroyed(dialogNode, () => { + let doCompletion = () => setTimeout(resolveOuter, 0); + info("Now checking if dialog is destroyed"); + + if (CONTENT_PROMPT_SUBDIALOG) { + ok( + !dialogNode.ownerGlobal || dialogNode.ownerGlobal.closed, + "onbeforeunload dialog should be gone." + ); + if (dialogNode.ownerGlobal && !dialogNode.ownerGlobal.closed) { + dialogNode.acceptDialog(); + } + } else { + ok(!dialogNode.parentNode, "onbeforeunload dialog should be gone."); + if (dialogNode.parentNode) { + // Failed to remove onbeforeunload dialog, so do it ourselves: + let leaveBtn = dialogNode.querySelector(".tabmodalprompt-button0"); + waitForDialogDestroyed(dialogNode, doCompletion); + EventUtils.synthesizeMouseAtCenter(leaveBtn, {}); + return; + } + } + + doCompletion(); + }); + // Click again: + testTab.closeButton.click(); + }); + // Click once: + testTab.closeButton.click(); + }); + await TestUtils.waitForCondition(() => !testTab.parentNode); + ok(!testTab.parentNode, "Tab should be closed completely"); +}); + +registerCleanupFunction(async function () { + if (testTab.parentNode) { + // Remove the handler, or closing this tab will prove tricky: + try { + await SpecialPowers.spawn(testTab.linkedBrowser, [], function () { + content.window.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 0000000000..04373e7ce2 --- /dev/null +++ b/browser/base/content/test/general/browser_drag.js @@ -0,0 +1,64 @@ +async function test() { + waitForExplicitFinish(); + + let EventUtils = {}; + Services.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.setPageProxyState("valid"); + let result = await EventUtils.synthesizePlainDragAndCancel( + { + srcElement: document.getElementById("identity-icon-box"), + }, + expected + ); + ok(result === true, "dragging dataTransfer should be expected"); + gURLBar.setPageProxyState(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 = BrowserTestUtils.addTab(gBrowser, "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 0000000000..b0c65c6af6 --- /dev/null +++ b/browser/base/content/test/general/browser_duplicateIDs.js @@ -0,0 +1,11 @@ +function test() { + var ids = {}; + Array.prototype.forEach.call( + 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_findbarClose.js b/browser/base/content/test/general/browser_findbarClose.js new file mode 100644 index 0000000000..e0fe2fcb98 --- /dev/null +++ b/browser/base/content/test/general/browser_findbarClose.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests find bar auto-close behavior + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +add_task(async function findbar_test() { + let newTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + gBrowser.selectedTab = newTab; + + let url = TEST_PATH + "test_bug628179.html"; + let promise = BrowserTestUtils.browserLoaded( + newTab.linkedBrowser, + false, + url + ); + BrowserTestUtils.loadURIString(newTab.linkedBrowser, url); + await promise; + + await gFindBarPromise; + gFindBar.open(); + + await new ContentTask.spawn(newTab.linkedBrowser, null, async function () { + let iframe = content.document.getElementById("iframe"); + let awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false); + iframe.src = "https://example.org/"; + await awaitLoad; + }); + + ok( + !gFindBar.hidden, + "the Find bar isn't hidden after the location of a subdocument changes" + ); + + let findBarClosePromise = BrowserTestUtils.waitForEvent( + gBrowser, + "findbarclose" + ); + gFindBar.close(); + await findBarClosePromise; + + gBrowser.removeTab(newTab); +}); 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 0000000000..9cf1f113f5 --- /dev/null +++ b/browser/base/content/test/general/browser_focusonkeydown.js @@ -0,0 +1,34 @@ +add_task(async function () { + let keyUps = 0; + + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<body>" + ); + + gURLBar.focus(); + + window.addEventListener( + "keyup", + function (event) { + if (event.originalTarget == gURLBar.inputField) { + keyUps++; + } + }, + { capture: true, once: true } + ); + + gURLBar.addEventListener( + "keydown", + function (event) { + gBrowser.selectedBrowser.focus(); + }, + { capture: true, once: true } + ); + + EventUtils.sendString("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 0000000000..2b21e34e92 --- /dev/null +++ b/browser/base/content/test/general/browser_fullscreen-window-open.js @@ -0,0 +1,366 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +const PREF_DISABLE_OPEN_NEW_WINDOW = + "browser.link.open_newwindow.disabled_in_fullscreen"; +const PREF_BLOCK_TOPLEVEL_DATA = + "security.data_uri.block_toplevel_data_uri_navigations"; +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/" +); + +var newWin; +var newBrowser; + +async function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true); + Services.prefs.setBoolPref(PREF_BLOCK_TOPLEVEL_DATA, false); + + newWin = await BrowserTestUtils.openNewBrowserWindow(); + newBrowser = newWin.gBrowser; + await promiseTabLoadEvent(newBrowser.selectedTab, gHttpTestRoot + TEST_FILE); + + // Enter browser fullscreen mode. + newWin.BrowserFullScreen(); + + runNextTest(); +} + +registerCleanupFunction(async function () { + // Exit browser fullscreen mode. + newWin.BrowserFullScreen(); + + await BrowserTestUtils.closeWindow(newWin); + + Services.prefs.clearUserPref(PREF_DISABLE_OPEN_NEW_WINDOW); + Services.prefs.clearUserPref(PREF_BLOCK_TOPLEVEL_DATA); +}); + +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() {}, + }); +} + +// 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() {}, + }); +} + +// 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() {}, + }); +} + +// Test for window.open() with outerWidth/Height. +function test_open_with_outerSize() { + let [outerWidth, outerHeight] = [newWin.outerWidth, newWin.outerHeight]; + waitForTabOpen({ + message: { + title: "test_open_with_outerSize", + param: "outerWidth=200,outerHeight=200", + }, + successFn() { + is(newWin.outerWidth, outerWidth, "Don't change window.outerWidth."); + is(newWin.outerHeight, outerHeight, "Don't change window.outerHeight."); + }, + finalizeFn() {}, + }); +} + +// Test for window.open() with innerWidth/Height. +function test_open_with_innerSize() { + let [innerWidth, innerHeight] = [newWin.innerWidth, newWin.innerHeight]; + waitForTabOpen({ + message: { + title: "test_open_with_innerSize", + param: "innerWidth=200,innerHeight=200", + }, + successFn() { + is(newWin.innerWidth, innerWidth, "Don't change window.innerWidth."); + is(newWin.innerHeight, innerHeight, "Don't change window.innerHeight."); + }, + finalizeFn() {}, + }); +} + +// Test for window.open() with dialog. +function test_open_with_dialog() { + waitForTabOpen({ + message: { + title: "test_open_with_dialog", + param: "dialog=yes", + }, + finalizeFn() {}, + }); +} + +// 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() { + 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() { + 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: "", + option: "noopener", + }, + finalizeFn() {}, + }); +} + +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) { + newBrowser.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(); + } + + newBrowser.removeTab(tab); + finalize(); + }); + }; + newBrowser.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, + AppConstants.BROWSER_CHROME_URL, + { + onSuccess: aOptions.successFn, + onFinalize, + } + ); + Services.wm.addListener(listener); + + executeWindowOpenInContent({ + uri: url, + title: message.title, + option: message.param, + }); +} + +function executeWindowOpenInContent(aParam) { + SpecialPowers.spawn( + newBrowser.selectedBrowser, + [JSON.stringify(aParam)], + async 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, + AppConstants.BROWSER_CHROME_URL, + { + onSuccess: aOptions.successFn, + onFinalize, + } + ); + Services.wm.addListener(listener); + + newWin.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(aXULWindow) { + Services.wm.removeListener(this); + + let domwindow = aXULWindow.docShell.domWindow; + 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(() => { + domwindow.close(); + executeSoon(this.callBack_onFinalize); + }, 3000); + } else { + domwindow.close(); + executeSoon(this.callBack_onFinalize); + } + }; + domwindow.addEventListener("load", onLoad, true); + }, + onCloseWindow(aXULWindow) {}, + QueryInterface: ChromeUtils.generateQI(["nsIWindowMediatorListener"]), +}; 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 0000000000..d8f0331268 --- /dev/null +++ b/browser/base/content/test/general/browser_gestureSupport.js @@ -0,0 +1,1132 @@ +/* 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/. */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +// Simple gestures tests +// +// Some of 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."; +var test_normalTab; + +async function test() { + waitForExplicitFinish(); + + // Disable the default gestures support during this part of the test + gGestureSupport.init(false); + + test_utils = window.windowUtils; + + // 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); + + const aPage = "about:about"; + test_normalTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + aPage, + true /* waitForLoad */ + ); + + // Test Firefox's gestures support. + test_commandset = document.getElementById("mainCommandSet"); + await test_swipeGestures(); + await test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture"); + await 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(60, 60, false, false), + "evt.target (" + evt.target + ") does not match expected value" + ); + is( + evt.clientX, + 60, + "evt.clientX (" + evt.clientX + ") does not match expected value" + ); + is( + evt.clientY, + 60, + "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 & Event.SHIFT_MASK) != 0, + "evt.shiftKey did not match expected value" + ); + is( + evt.ctrlKey, + (test_expectedModifiers & Event.CONTROL_MASK) != 0, + "evt.ctrlKey did not match expected value" + ); + is( + evt.altKey, + (test_expectedModifiers & Event.ALT_MASK) != 0, + "evt.altKey did not match expected value" + ); + is( + evt.metaKey, + (test_expectedModifiers & Event.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, 60, 60, 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, 60, 60, 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 = Event.SHIFT_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); + + // event.metaKey + modifier = Event.META_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); + + // event.altKey + modifier = Event.ALT_MASK; + e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier); + + // event.ctrlKey + modifier = Event.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. +async 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. + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Start", + 10, + 10, + 0, + initialDelta, + 0, + 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); + + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + delta, + 0, + 0 + ); + cumulativeDelta += delta; + checkBoth( + 2, + "Increasing command was triggered", + "Decreasing command was triggered" + ); + } + + // Now go back in the opposite direction. + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + -initialDelta, + 0, + 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); + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + delta, + 0, + 0 + ); + cumulativeDelta += delta; + checkBoth( + 4, + "Increasing command was triggered", + "Decreasing command was triggered" + ); + } + + // Go back to the original direction. The original command should trigger. + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + initialDelta, + 0, + 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. + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix, + 10, + 10, + 0, + cumulativeDelta, + 0, + 0 + ); + checkBoth( + 6, + "Increasing command was triggered", + "Decreasing command was triggered" + ); +} + +function test_addCommand(prefName, id) { + let cmd = test_commandset.appendChild(document.createXULElement("command")); + cmd.setAttribute("id", id); + cmd.setAttribute("oncommand", "this.callCount++;"); + + cmd.origPrefName = prefName; + cmd.origPrefValue = Services.prefs.getCharPref(prefName); + Services.prefs.setCharPref(prefName, id); + + return cmd; +} + +function test_removeCommand(cmd) { + Services.prefs.setCharPref(cmd.origPrefName, cmd.origPrefValue); + test_commandset.removeChild(cmd); +} + +// Test whether latched events are only called once per direction of motion. +async function test_latchedGesture(gesture, inc, dec, eventPrefix) { + let branch = test_prefBranch + gesture + "."; + + // Put the gesture into latched mode. + let oldLatchedValue = Services.prefs.getBoolPref(branch + "latched"); + Services.prefs.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. + await test_emitLatchedEvents(eventPrefix, 500, cmd); + await test_emitLatchedEvents(eventPrefix, -500, cmd); + + // Restore the gesture to its original configuration. + Services.prefs.setBoolPref(branch + "latched", oldLatchedValue); + for (let dir in cmd) { + test_removeCommand(cmd[dir]); + } +} + +// Test whether non-latched events are triggered upon sufficient motion. +async function test_thresholdGesture(gesture, inc, dec, eventPrefix) { + let branch = test_prefBranch + gesture + "."; + + // Disable latched mode for this gesture. + let oldLatchedValue = Services.prefs.getBoolPref(branch + "latched"); + Services.prefs.setBoolPref(branch + "latched", false); + + // Set the triggering threshold value to 50. + let oldThresholdValue = Services.prefs.getIntPref(branch + "threshold"); + Services.prefs.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; + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Start", + 10, + 10, + 0, + 49.5, + 0, + 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; + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + 1, + 0, + 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; + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + -49.5, + 0, + 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; + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix + "Update", + 10, + 10, + 0, + -1.5, + 0, + 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; + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + eventPrefix, + 0, + 0, + 0, + -0.5, + 0, + 0 + ); + ok(cmdInc.callCount == 0, "Increasing command was triggered"); + ok(cmdDec.callCount == 0, "Decreasing command was triggered"); + + // Restore the gesture to its original configuration. + Services.prefs.setBoolPref(branch + "latched", oldLatchedValue); + Services.prefs.setIntPref(branch + "threshold", oldThresholdValue); + test_removeCommand(cmdInc); + test_removeCommand(cmdDec); +} + +async 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(); + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + "MozSwipeGesture", + 10, + 10, + up, + 0, + 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(); + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + "MozSwipeGesture", + 10, + 10, + down, + 0, + 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(); + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + "MozSwipeGesture", + 10, + 10, + left, + 0, + 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(); + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + "MozSwipeGesture", + 10, + 10, + right, + 0, + 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(); + await synthesizeSimpleGestureEvent( + test_normalTab.linkedBrowser, + "MozSwipeGesture", + 10, + 10, + combos[i], + 0, + 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).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; +} + +async 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 + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGestureStart", + 10, + 10, + aDirection, + 0.001, + 0, + 0 + ); + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGestureUpdate", + 10, + 10, + aDirection, + delta, + 0, + 0 + ); + aImageElement.clientTop; + + // If stop, check intermediate + if (aStop) { + // Send near-zero-delta to stop, and force flush + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGestureUpdate", + 10, + 10, + aDirection, + 0.001, + 0, + 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 + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGesture", + 10, + 10, + aDirection, + 0, + 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") + ); +} + +async function test_rotateGesturesOnTab() { + gBrowser.selectedBrowser.removeEventListener( + "load", + test_rotateGesturesOnTab, + true + ); + + if (!ImageDocument.isInstance(content.document)) { + ok(false, "Image document failed to open for rotation testing"); + gBrowser.removeTab(test_imageTab); + BrowserTestUtils.removeTab(test_normalTab); + test_imageTab = null; + test_normalTab = null; + 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); + BrowserTestUtils.removeTab(test_normalTab); + test_imageTab = null; + test_normalTab = null; + 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 + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 0), + cl, + 35, + true + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 0), + cl, + 35, + false + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 90), + cl, + 55, + true + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 180), + cl, + 55, + false + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 270), + ccl, + 35, + true + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 270), + ccl, + 35, + false + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 180), + ccl, + 55, + true + ); + await test_rotateHelperOneGesture( + imgElem, + normRot(initRot + 90), + ccl, + 55, + false + ); + + // Manually rotate it 90 degrees clockwise to prepare for next iteration, + // and force flush + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGestureStart", + 10, + 10, + cl, + 0.001, + 0, + 0 + ); + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGestureUpdate", + 10, + 10, + cl, + 90, + 0, + 0 + ); + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGestureUpdate", + 10, + 10, + cl, + 0.001, + 0, + 0 + ); + await synthesizeSimpleGestureEvent( + test_imageTab.linkedBrowser, + "MozRotateGesture", + 10, + 10, + cl, + 0, + 0, + 0 + ); + imgElem.clientTop; + } + + gBrowser.removeTab(test_imageTab); + BrowserTestUtils.removeTab(test_normalTab); + test_imageTab = null; + test_normalTab = null; + finish(); +} + +function test_rotateGestures() { + test_imageTab = BrowserTestUtils.addTab( + gBrowser, + "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_hide_removing.js b/browser/base/content/test/general/browser_hide_removing.js new file mode 100644 index 0000000000..24079c22e6 --- /dev/null +++ b/browser/base/content/test/general/browser_hide_removing.js @@ -0,0 +1,27 @@ +/* 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 + +add_task(async function () { + // Add a tab that will get removed and hidden + let testTab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs"); + await BrowserTestUtils.switchTab(gBrowser, testTab); + + let numVisBeforeHide, numVisAfterHide; + + // We have to animate the tab removal in order to get an async + // tab close. + BrowserTestUtils.removeTab(testTab, { animate: true }); + + numVisBeforeHide = gBrowser.visibleTabs.length; + gBrowser.hideTab(testTab); + numVisAfterHide = gBrowser.visibleTabs.length; + + is(numVisBeforeHide, 1, "animated remove has in 1 tab left"); + is(numVisAfterHide, 1, "hiding a removing tab also has 1 tab"); +}); 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 0000000000..81dc48d3e4 --- /dev/null +++ b/browser/base/content/test/general/browser_homeDrop.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function setupHomeButton() { + // Put the home button in the pre-proton placement to test focus states. + CustomizableUI.addWidgetToArea( + "home-button", + "nav-bar", + CustomizableUI.getPlacementOfWidget("stop-reload-button").position + 1 + ); + CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar"); + registerCleanupFunction(async function resetToolbar() { + await CustomizableUI.reset(); + }); +}); + +add_task(async function () { + let HOMEPAGE_PREF = "browser.startup.homepage"; + + await pushPrefs([HOMEPAGE_PREF, "about:mozilla"]); + + let EventUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils + ); + + // Since synthesizeDrop triggers the srcElement, need to use another button + // that should be visible. + let dragSrcElement = document.getElementById("sidebar-button"); + ok(dragSrcElement, "Sidebar button exists"); + let homeButton = document.getElementById("home-button"); + ok(homeButton, "home button present"); + + async function drop(dragData, homepage) { + let setHomepageDialogPromise = + BrowserTestUtils.promiseAlertDialogOpen("accept"); + let setHomepagePromise = TestUtils.waitForPrefChange( + HOMEPAGE_PREF, + newVal => newVal == homepage + ); + + EventUtils.synthesizeDrop( + dragSrcElement, + homeButton, + dragData, + "copy", + window + ); + + // Ensure dnd suppression is cleared. + EventUtils.synthesizeMouseAtCenter(homeButton, { type: "mouseup" }, window); + + await setHomepageDialogPromise; + ok(true, "dialog appeared in response to home button drop"); + + await setHomepagePromise; + + let modified = Services.prefs.getStringPref(HOMEPAGE_PREF); + is(modified, homepage, "homepage is set correctly"); + Services.prefs.setStringPref(HOMEPAGE_PREF, "about:mozilla;"); + } + + function dropInvalidURI() { + return new Promise(resolve => { + let consoleListener = { + observe(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 + ); + // Ensure dnd suppression is cleared. + EventUtils.synthesizeMouseAtCenter( + homeButton, + { type: "mouseup" }, + window + ); + }); + }); + } + + await drop( + [[{ type: "text/plain", data: "http://mochi.test:8888/" }]], + "http://mochi.test:8888/" + ); + await 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" + ); + await dropInvalidURI(); +}); 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 0000000000..1624a1514d --- /dev/null +++ b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js @@ -0,0 +1,48 @@ +"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(async function checkBackFromInvalidURI() { + await pushPrefs(["keyword.enabled", false]); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:robots", + true + ); + info("Loaded about:robots"); + + gURLBar.value = "::2600"; + + let promiseErrorPageLoaded = BrowserTestUtils.waitForErrorPage( + tab.linkedBrowser + ); + gURLBar.handleCommand(); + await 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(); + await promiseOtherPageLoaded; + ok( + gBrowser.webNavigation.canGoForward, + "Should be able to go forward from previous page." + ); + } + BrowserTestUtils.removeTab(tab); +}); 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 0000000000..631fcb3bfe --- /dev/null +++ b/browser/base/content/test/general/browser_lastAccessedTab.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +// 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(); + // This test assumes that time passes between operations. But if the precision + // is low enough, and the test fast enough, an operation, and a successive call + // to Date.now() will have the same time value. + SpecialPowers.pushPrefEnv( + { set: [["privacy.reduceTimerPrecision", false]] }, + function () { + originalTab = gBrowser.selectedTab; + nextStep(step2); + } + ); +} + +function step2() { + isCurrent(originalTab, "selected tab has the current timestamp"); + newTab = BrowserTestUtils.addTab(gBrowser, "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_menuButtonFitts.js b/browser/base/content/test/general/browser_menuButtonFitts.js new file mode 100644 index 0000000000..f56f46eb6c --- /dev/null +++ b/browser/base/content/test/general/browser_menuButtonFitts.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +function getNavBarEndPosition() { + let navBar = document.getElementById("nav-bar"); + let boundingRect = navBar.getBoundingClientRect(); + + // Find where the nav-bar is vertically. + let y = boundingRect.top + Math.floor(boundingRect.height / 2); + // Use the last pixel of the screen since it is maximized. + let x = boundingRect.width - 1; + return { x, y }; +} + +/** + * Clicking the right end of a maximized window should open the hamburger menu. + */ +add_task(async function test_clicking_hamburger_edge_fitts() { + if (window.windowState != window.STATE_MAXIMIZED) { + info(`Waiting for maximize, current state: ${window.windowState}`); + let resizeDone = BrowserTestUtils.waitForEvent( + window, + "resize", + false, + () => window.outerWidth >= screen.width - 1 + ); + let maximizeDone = BrowserTestUtils.waitForEvent(window, "sizemodechange"); + window.maximize(); + await maximizeDone; + await resizeDone; + } + + is(window.windowState, window.STATE_MAXIMIZED, "should be maximized"); + + let { x, y } = getNavBarEndPosition(); + info(`Clicking in ${x}, ${y}`); + + let popupHiddenResolve; + let popupHiddenPromise = new Promise(resolve => { + popupHiddenResolve = resolve; + }); + async function onPopupHidden() { + PanelUI.panel.removeEventListener("popuphidden", onPopupHidden); + + info("Waiting for restore"); + + let restoreDone = BrowserTestUtils.waitForEvent(window, "sizemodechange"); + window.restore(); + await restoreDone; + + popupHiddenResolve(); + } + 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(x, y, {}, window); + await popupHiddenPromise; +}); 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 0000000000..f023b78909 --- /dev/null +++ b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js @@ -0,0 +1,49 @@ +/* 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(async function () { + await pushPrefs([middleMousePastePref, true], [autoScrollPref, false]); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + let url = "javascript:http://www.example.com/"; + await new Promise((resolve, reject) => { + SimpleTest.waitForClipboard( + url, + () => { + Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.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"); + await BrowserTestUtils.synthesizeMouse( + null, + 10, + 10, + { button: 1 }, + gBrowser.selectedBrowser + ); + await 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 0000000000..a57fea079c --- /dev/null +++ b/browser/base/content/test/general/browser_minimize.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function () { + registerCleanupFunction(function () { + window.restore(); + }); + function isActive() { + return gBrowser.selectedTab.linkedBrowser.docShellIsActive; + } + + ok(isActive(), "Docshell should be active when starting the test"); + ok(!document.hidden, "Top level window should be visible"); + + info("Calling window.minimize"); + let promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.minimize(); + await promiseSizeModeChange; + ok(!isActive(), "Docshell should be Inactive"); + ok(document.hidden, "Top level window should be hidden"); + + info("Calling window.restore"); + promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.restore(); + // On Ubuntu `window.restore` doesn't seem to work, use a timer to make the + // test fail faster and more cleanly than with a test timeout. + await Promise.race([ + promiseSizeModeChange, + new Promise((resolve, reject) => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + reject("timed out waiting for sizemodechange event"); + }, 5000) + ), + ]); + // The sizemodechange event can sometimes be fired before the + // occlusionstatechange event, especially in chaos mode. + if (window.isFullyOccluded) { + await BrowserTestUtils.waitForEvent(window, "occlusionstatechange"); + } + ok(isActive(), "Docshell should be active again"); + ok(!document.hidden, "Top level window should be visible"); +}); 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 0000000000..be3de519d6 --- /dev/null +++ b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js @@ -0,0 +1,42 @@ +"use strict"; + +const kURL = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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(async function () { + await BrowserTestUtils.withNewTab(kURL, async function (browser) { + let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + await SpecialPowers.spawn(browser, [], async function () { + let a = content.document.createElement("a"); + // newTabPromise won't resolve until it has a URL that's not "about:blank". + // But doing document.open() from inside that same document does not change + // the URL of the docshell. So we need to do some URL change to cause + // newTabPromise to resolve, since the document is at about:blank the whole + // time, URL-wise. Navigating to '#' should do the trick without changing + // anything else about the document involved. + a.href = + "javascript:document.write('spoof'); location.href='#'; void(0);"; + a.textContent = "Some link"; + content.document.body.appendChild(a); + }); + info("Added element"); + await BrowserTestUtils.synthesizeMouseAtCenter("a", { button: 1 }, browser); + let newTab = await newTabPromise; + is( + newTab.linkedBrowser.contentPrincipal.origin, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com", + "Principal should be for example.com" + ); + await BrowserTestUtils.switchTab(gBrowser, newTab); + info(gURLBar.value); + isnot(gURLBar.value, "", "URL bar should not be empty."); + 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 0000000000..d0e7f35c1f --- /dev/null +++ b/browser/base/content/test/general/browser_newTabDrop.js @@ -0,0 +1,221 @@ +const ANY_URL = undefined; + +const { SearchTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SearchTestUtils.sys.mjs" +); + +SearchTestUtils.init(this); + +registerCleanupFunction(async function cleanup() { + while (gBrowser.tabs.length > 1) { + BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + } +}); + +add_task(async function test_setup() { + // This test opens multiple tabs and some confirm dialogs, that takes long. + requestLongerTimeout(2); + + // Stop search-engine loads from hitting the network + await SearchTestUtils.installSearchExtension( + { + name: "MozSearch", + search_url: "https://example.com/", + search_url_get_params: "q={searchTerms}", + }, + { setAsDefault: true } + ); +}); + +// New Tab Button opens any link. +add_task(async function single_url() { + await dropText("mochi.test/first", ["http://mochi.test/first"]); +}); +add_task(async function single_url2() { + await dropText("mochi.test/second", ["http://mochi.test/second"]); +}); +add_task(async function single_url3() { + await dropText("mochi.test/third", ["http://mochi.test/third"]); +}); + +// Single text/plain item, with multiple links. +add_task(async function multiple_urls() { + await dropText("www.mochi.test/1\nmochi.test/2", [ + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.mochi.test/1", + "http://mochi.test/2", + ]); +}); + +// Multiple text/plain items, with single and multiple links. +add_task(async function multiple_items_single_and_multiple_links() { + await drop( + [ + [{ type: "text/plain", data: "mochi.test/5" }], + [{ type: "text/plain", data: "mochi.test/6\nmochi.test/7" }], + ], + ["http://mochi.test/5", "http://mochi.test/6", "http://mochi.test/7"] + ); +}); + +// Single text/x-moz-url item, with multiple links. +// "text/x-moz-url" has titles in even-numbered lines. +add_task(async function single_moz_url_multiple_links() { + await drop( + [ + [ + { + type: "text/x-moz-url", + data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9", + }, + ], + ], + ["http://mochi.test/8", "http://mochi.test/9"] + ); +}); + +// Single item with multiple types. +add_task(async function single_item_multiple_types() { + await drop( + [ + [ + { type: "text/plain", data: "mochi.test/10" }, + { type: "text/x-moz-url", data: "mochi.test/11\nTITLE11" }, + ], + ], + ["http://mochi.test/11"] + ); +}); + +// Warn when too many URLs are dropped. +add_task(async function multiple_tabs_under_max() { + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/multi" + i); + } + await dropText(urls.join("\n"), [ + "http://mochi.test/multi0", + "http://mochi.test/multi1", + "http://mochi.test/multi2", + "http://mochi.test/multi3", + "http://mochi.test/multi4", + ]); +}); +add_task(async function multiple_tabs_over_max_accept() { + await pushPrefs(["browser.tabs.maxOpenBeforeWarn", 4]); + + let confirmPromise = BrowserTestUtils.promiseAlertDialog("accept"); + + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/accept" + i); + } + await dropText(urls.join("\n"), [ + "http://mochi.test/accept0", + "http://mochi.test/accept1", + "http://mochi.test/accept2", + "http://mochi.test/accept3", + "http://mochi.test/accept4", + ]); + + await confirmPromise; + + await popPrefs(); +}); +add_task(async function multiple_tabs_over_max_cancel() { + await pushPrefs(["browser.tabs.maxOpenBeforeWarn", 4]); + + let confirmPromise = BrowserTestUtils.promiseAlertDialog("cancel"); + + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/cancel" + i); + } + await dropText(urls.join("\n"), []); + + await confirmPromise; + + await popPrefs(); +}); + +// Open URLs ignoring non-URL. +add_task(async function multiple_urls() { + await dropText( + ` + mochi.test/urls0 + mochi.test/urls1 + mochi.test/urls2 + non url0 + mochi.test/urls3 + non url1 + non url2 +`, + [ + "http://mochi.test/urls0", + "http://mochi.test/urls1", + "http://mochi.test/urls2", + "http://mochi.test/urls3", + ] + ); +}); + +// Open single search if there's no URL. +add_task(async function multiple_text() { + await dropText( + ` + non url0 + non url1 + non url2 +`, + [ANY_URL] + ); +}); + +function dropText(text, expectedURLs) { + return drop([[{ type: "text/plain", data: text }]], expectedURLs); +} + +async function drop(dragData, expectedURLs) { + let dragDataString = JSON.stringify(dragData); + info( + `Starting test for dragData:${dragDataString}; expectedURLs.length:${expectedURLs.length}` + ); + let EventUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils + ); + + // Since synthesizeDrop triggers the srcElement, need to use another button + // that should be visible. + let dragSrcElement = document.getElementById("back-button"); + ok(dragSrcElement, "Back button exists"); + let newTabButton = document.getElementById( + gBrowser.tabContainer.hasAttribute("overflow") + ? "new-tab-button" + : "tabs-newtab-button" + ); + ok(newTabButton, "New Tab button exists"); + + let awaitDrop = BrowserTestUtils.waitForEvent(newTabButton, "drop"); + + let loadedPromises = expectedURLs.map(url => + BrowserTestUtils.waitForNewTab(gBrowser, url, false, true) + ); + + EventUtils.synthesizeDrop( + dragSrcElement, + newTabButton, + dragData, + "link", + window + ); + + let tabs = await Promise.all(loadedPromises); + for (let tab of tabs) { + BrowserTestUtils.removeTab(tab); + } + + await 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 0000000000..243b691873 --- /dev/null +++ b/browser/base/content/test/general/browser_newWindowDrop.js @@ -0,0 +1,230 @@ +const { SearchTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SearchTestUtils.sys.mjs" +); + +SearchTestUtils.init(this); + +add_task(async function test_setup() { + // Opening multiple windows on debug build takes too long time. + requestLongerTimeout(10); + + // Stop search-engine loads from hitting the network + await SearchTestUtils.installSearchExtension( + { + name: "MozSearch", + search_url: "https://example.com/", + search_url_get_params: "q={searchTerms}", + }, + { setAsDefault: true } + ); + + // Move New Window button to nav bar, to make it possible to drag and drop. + let { CustomizableUI } = ChromeUtils.importESModule( + "resource:///modules/CustomizableUI.sys.mjs" + ); + 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.removeWidgetFromArea("new-window-button"); + }); + } + + CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar"); + registerCleanupFunction(() => + CustomizableUI.removeWidgetFromArea("sidebar-button") + ); +}); + +// New Window Button opens any link. +add_task(async function single_url() { + await dropText("mochi.test/first", ["http://mochi.test/first"]); +}); +add_task(async function single_javascript() { + await dropText("javascript:'bad'", ["about:blank"]); +}); +add_task(async function single_javascript_capital() { + await dropText("jAvascript:'bad'", ["about:blank"]); +}); +add_task(async function single_url2() { + await dropText("mochi.test/second", ["http://mochi.test/second"]); +}); +add_task(async function single_data_url() { + await dropText("data:text/html,bad", ["data:text/html,bad"]); +}); +add_task(async function single_url3() { + await dropText("mochi.test/third", ["http://mochi.test/third"]); +}); + +// Single text/plain item, with multiple links. +add_task(async function multiple_urls() { + await dropText("mochi.test/1\nmochi.test/2", [ + "http://mochi.test/1", + "http://mochi.test/2", + ]); +}); +add_task(async function multiple_urls_javascript() { + await dropText("javascript:'bad1'\nmochi.test/3", [ + "about:blank", + "http://mochi.test/3", + ]); +}); +add_task(async function multiple_urls_data() { + await dropText("mochi.test/4\ndata:text/html,bad1", [ + "http://mochi.test/4", + "data:text/html,bad1", + ]); +}); + +// Multiple text/plain items, with single and multiple links. +add_task(async function multiple_items_single_and_multiple_links() { + await drop( + [ + [{ type: "text/plain", data: "mochi.test/5" }], + [{ type: "text/plain", data: "mochi.test/6\nmochi.test/7" }], + ], + ["http://mochi.test/5", "http://mochi.test/6", "http://mochi.test/7"] + ); +}); + +// Single text/x-moz-url item, with multiple links. +// "text/x-moz-url" has titles in even-numbered lines. +add_task(async function single_moz_url_multiple_links() { + await drop( + [ + [ + { + type: "text/x-moz-url", + data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9", + }, + ], + ], + ["http://mochi.test/8", "http://mochi.test/9"] + ); +}); + +// Single item with multiple types. +add_task(async function single_item_multiple_types() { + await drop( + [ + [ + { type: "text/plain", data: "mochi.test/10" }, + { type: "text/x-moz-url", data: "mochi.test/11\nTITLE11" }, + ], + ], + ["http://mochi.test/11"] + ); +}); + +// Warn when too many URLs are dropped. +add_task(async function multiple_tabs_under_max() { + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/multi" + i); + } + await dropText(urls.join("\n"), [ + "http://mochi.test/multi0", + "http://mochi.test/multi1", + "http://mochi.test/multi2", + "http://mochi.test/multi3", + "http://mochi.test/multi4", + ]); +}); +add_task(async function multiple_tabs_over_max_accept() { + await pushPrefs(["browser.tabs.maxOpenBeforeWarn", 4]); + + let confirmPromise = BrowserTestUtils.promiseAlertDialog("accept"); + + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/accept" + i); + } + await dropText( + urls.join("\n"), + [ + "http://mochi.test/accept0", + "http://mochi.test/accept1", + "http://mochi.test/accept2", + "http://mochi.test/accept3", + "http://mochi.test/accept4", + ], + true + ); + + await confirmPromise; + + await popPrefs(); +}); +add_task(async function multiple_tabs_over_max_cancel() { + await pushPrefs(["browser.tabs.maxOpenBeforeWarn", 4]); + + let confirmPromise = BrowserTestUtils.promiseAlertDialog("cancel"); + + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/cancel" + i); + } + await dropText(urls.join("\n"), [], true); + + await confirmPromise; + + await popPrefs(); +}); + +function dropText(text, expectedURLs, ignoreFirstWindow = false) { + return drop( + [[{ type: "text/plain", data: text }]], + expectedURLs, + ignoreFirstWindow + ); +} + +async function drop(dragData, expectedURLs, ignoreFirstWindow = false) { + let dragDataString = JSON.stringify(dragData); + info( + `Starting test for dragData:${dragDataString}; expectedURLs.length:${expectedURLs.length}` + ); + let EventUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils + ); + + // Since synthesizeDrop triggers the srcElement, need to use another button + // that should be visible. + let dragSrcElement = document.getElementById("sidebar-button"); + ok(dragSrcElement, "Sidebar button exists"); + let newWindowButton = document.getElementById("new-window-button"); + ok(newWindowButton, "New Window button exists"); + + let awaitDrop = BrowserTestUtils.waitForEvent(newWindowButton, "drop"); + + let loadedPromises = expectedURLs.map(url => + BrowserTestUtils.waitForNewWindow({ + url, + anyWindow: true, + maybeErrorPage: true, + }) + ); + + EventUtils.synthesizeDrop( + dragSrcElement, + newWindowButton, + dragData, + "link", + window + ); + + let windows = await Promise.all(loadedPromises); + for (let window of windows) { + await BrowserTestUtils.closeWindow(window); + } + + await awaitDrop; + ok(true, "Got drop event"); +} diff --git a/browser/base/content/test/general/browser_new_http_window_opened_from_file_tab.js b/browser/base/content/test/general/browser_new_http_window_opened_from_file_tab.js new file mode 100644 index 0000000000..8e9f458073 --- /dev/null +++ b/browser/base/content/test/general/browser_new_http_window_opened_from_file_tab.js @@ -0,0 +1,63 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ + +const TEST_FILE = "file_with_link_to_http.html"; +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const TEST_HTTP = "http://example.org/"; + +// Test for bug 1338375. +add_task(async function () { + // Open file:// page. + let dir = getChromeDir(getResolvedURI(gTestPath)); + dir.append(TEST_FILE); + const uriString = Services.io.newFileURI(dir).spec; + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uriString); + registerCleanupFunction(async function () { + BrowserTestUtils.removeTab(tab); + }); + let browser = tab.linkedBrowser; + + // Set pref to open in new window. + Services.prefs.setIntPref("browser.link.open_newwindow", 2); + registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.link.open_newwindow"); + }); + + // Open new http window from JavaScript in file:// page and check that we get + // a new window with the correct page and features. + let promiseNewWindow = BrowserTestUtils.waitForNewWindow({ url: TEST_HTTP }); + await SpecialPowers.spawn(browser, [TEST_HTTP], uri => { + content.open(uri, "_blank"); + }); + let win = await promiseNewWindow; + registerCleanupFunction(async function () { + await BrowserTestUtils.closeWindow(win); + }); + ok(win, "Check that an http window loaded when using window.open."); + ok( + win.menubar.visible, + "Check that the menu bar on the new window is visible." + ); + ok( + win.toolbar.visible, + "Check that the tool bar on the new window is visible." + ); + + // Open new http window from a link in file:// page and check that we get a + // new window with the correct page and features. + promiseNewWindow = BrowserTestUtils.waitForNewWindow({ url: TEST_HTTP }); + await BrowserTestUtils.synthesizeMouseAtCenter("#linkToExample", {}, browser); + let win2 = await promiseNewWindow; + registerCleanupFunction(async function () { + await BrowserTestUtils.closeWindow(win2); + }); + ok(win2, "Check that an http window loaded when using link."); + ok( + win2.menubar.visible, + "Check that the menu bar on the new window is visible." + ); + ok( + win2.toolbar.visible, + "Check that the tool bar on the new window is visible." + ); +}); 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 0000000000..dbf99f1233 --- /dev/null +++ b/browser/base/content/test/general/browser_newwindow_focus.js @@ -0,0 +1,93 @@ +"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>`; + +/** + * 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(async function test_focus_browser() { + await BrowserTestUtils.withNewTab( + { + url: PAGE, + gBrowser, + }, + async function (browser) { + let newWinPromise = BrowserTestUtils.domWindowOpenedAndLoaded(null); + let delayedStartupPromise = BrowserTestUtils.waitForNewWindow(); + + await BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser); + let newWin = await newWinPromise; + await BrowserTestUtils.waitForContentEvent( + newWin.gBrowser.selectedBrowser, + "MozAfterPaint" + ); + await delayedStartupPromise; + + let focusedElement = Services.focus.getFocusedElementForWindow( + newWin, + false, + {} + ); + + Assert.equal( + focusedElement, + newWin.gBrowser.selectedBrowser, + "Initial browser should be focused" + ); + + await 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(async function test_no_steal_focus() { + await BrowserTestUtils.withNewTab( + { + url: PAGE, + gBrowser, + }, + async function (browser) { + let newWinPromise = BrowserTestUtils.domWindowOpenedAndLoaded(null); + let delayedStartupPromise = BrowserTestUtils.waitForNewWindow(); + + await BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser); + let newWin = await newWinPromise; + + // Because we're switching focus, we shouldn't steal it once + // content paints. + newWin.gURLBar.focus(); + + await BrowserTestUtils.waitForContentEvent( + newWin.gBrowser.selectedBrowser, + "MozAfterPaint" + ); + await delayedStartupPromise; + + let focusedElement = Services.focus.getFocusedElementForWindow( + newWin, + false, + {} + ); + + Assert.equal( + focusedElement, + newWin.gURLBar.inputField, + "URLBar should be focused" + ); + + await BrowserTestUtils.closeWindow(newWin); + } + ); +}); 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 0000000000..706f21387c --- /dev/null +++ b/browser/base/content/test/general/browser_plainTextLinks.js @@ -0,0 +1,237 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +function testExpected(expected, msg) { + is( + document.getElementById("context-openlinkincurrent").hidden, + expected, + msg + ); +} + +function testLinkExpected(expected, msg) { + is(gContextMenu.linkURL, expected, msg); +} + +add_task(async function () { + const url = + "data:text/html;charset=UTF-8,Test For Non-Hyperlinked url selection"; + await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + + // Initial setup of the content area. + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async 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"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + 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( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.example.com/", + "Linkified text should open the correct link" + ), + () => { + testExpected(false, "Link options should show for open-suse.ru"); + testLinkExpected( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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 = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ testid }], + async 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]; + } + ); + + // Trigger a mouse event until we receive the popupshown event. + let sawPopup = false; + let popupShownPromise = BrowserTestUtils.waitForEvent( + contentAreaContextMenu, + "popupshown", + false, + () => { + sawPopup = true; + return true; + } + ); + while (!sawPopup) { + await BrowserTestUtils.synthesizeMouseAtPoint( + menuPosition[0], + menuPosition[1], + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + if (!sawPopup) { + await new Promise(r => setTimeout(r, 100)); + } + } + await popupShownPromise; + + checks[testid](); + + // On Linux non-e10s it's possible the menu was closed by a focus-out event + // on the window. Work around this by calling hidePopup only if the menu + // hasn't been closed yet. See bug 1352709 comment 36. + if (contentAreaContextMenu.state === "closed") { + continue; + } + + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + contentAreaContextMenu, + "popuphidden" + ); + contentAreaContextMenu.hidePopup(); + await 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 0000000000..945e2bbd3a --- /dev/null +++ b/browser/base/content/test/general/browser_printpreview.js @@ -0,0 +1,43 @@ +let ourTab; + +async function test() { + waitForExplicitFinish(); + + BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true).then( + function (tab) { + ourTab = tab; + ok( + !document.querySelector(".printPreviewBrowser"), + "Should NOT be in print preview mode at starting this tests" + ); + testClosePrintPreviewWithEscKey(); + } + ); +} + +function tidyUp() { + BrowserTestUtils.removeTab(ourTab); + finish(); +} + +async function testClosePrintPreviewWithEscKey() { + await openPrintPreview(); + EventUtils.synthesizeKey("KEY_Escape"); + await checkPrintPreviewClosed(); + ok(true, "print preview mode should be finished by Esc key press"); + tidyUp(); +} + +async function openPrintPreview() { + document.getElementById("cmd_print").doCommand(); + await BrowserTestUtils.waitForCondition(() => { + let preview = document.querySelector(".printPreviewBrowser"); + return preview && BrowserTestUtils.is_visible(preview); + }); +} + +async function checkPrintPreviewClosed() { + await BrowserTestUtils.waitForCondition( + () => !document.querySelector(".printPreviewBrowser") + ); +} 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 0000000000..34a4c8bbf0 --- /dev/null +++ b/browser/base/content/test/general/browser_private_browsing_window.js @@ -0,0 +1,133 @@ +// 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" + ); + } + }); + + is( + privateWin.gBrowser.tabs[0].label, + "New Private Tab", + "New tabs in the private browsing windows should have 'New Private Tab' as the title." + ); + + 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" + ); + } + }); + + is( + privateWin.gBrowser.tabs[0].label, + "New Tab", + "Normal tab title is used also in the permanent private browsing mode." + ); + 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 0000000000..d8c9f8e7b5 --- /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_refreshBlocker.js b/browser/base/content/test/general/browser_refreshBlocker.js new file mode 100644 index 0000000000..0052282257 --- /dev/null +++ b/browser/base/content/test/general/browser_refreshBlocker.js @@ -0,0 +1,209 @@ +"use strict"; + +const META_PAGE = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org/browser/browser/base/content/test/general/refresh_meta.sjs"; +const HEADER_PAGE = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org/browser/browser/base/content/test/general/refresh_header.sjs"; +const TARGET_PAGE = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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 + */ +async function attemptFakeRefresh(browser, expectRefresh) { + await SpecialPowers.spawn( + browser, + [expectRefresh], + async function (contentExpectRefresh) { + let URI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI; + let refresher = docShell.QueryInterface(Ci.nsIRefreshURI); + refresher.refreshURI(URI, null, 0); + + 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(async function test_can_enable_and_block() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TARGET_PAGE, + }, + async function (browser) { + // By default, we should be able to reload the page. + await attemptFakeRefresh(browser, true); + + await pushPrefs(["accessibility.blockautorefresh", true]); + + let notificationPromise = BrowserTestUtils.waitForNotificationBar( + gBrowser, + browser, + "refresh-blocked" + ); + + await attemptFakeRefresh(browser, false); + + await notificationPromise; + + await pushPrefs(["accessibility.blockautorefresh", false]); + + // Page reloads should go through again. + await 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 + */ +async function testRealRefresh(refreshPage, delay) { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function (browser) { + await pushPrefs(["accessibility.blockautorefresh", true]); + + BrowserTestUtils.loadURIString( + browser, + refreshPage + "?p=" + TARGET_PAGE + "&d=" + delay + ); + await 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.getAttribute("value"), + "refresh-blocked", + "Should be showing the right notification" + ); + + // Then click the button to allow the refresh. + let buttons = notification.buttonContainer.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(); + + await refreshPromise; + } + ); +} + +/** + * Tests the meta-tag case for both short and longer delay times. + */ +add_task(async function test_can_allow_refresh() { + await testRealRefresh(META_PAGE, 0); + await testRealRefresh(META_PAGE, 100); + await testRealRefresh(META_PAGE, 500); +}); + +/** + * Tests that when a HTTP header case for both short and longer + * delay times. + */ +add_task(async function test_can_block_refresh_from_header() { + await testRealRefresh(HEADER_PAGE, 0); + await testRealRefresh(HEADER_PAGE, 100); + await testRealRefresh(HEADER_PAGE, 500); +}); + +/** + * Tests that we can update a notification when multiple reload/redirect + * attempts happen. + */ +add_task(async function test_can_update_notification() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function (browser) { + await pushPrefs(["accessibility.blockautorefresh", true]); + + // First, attempt a redirect + BrowserTestUtils.loadURIString( + browser, + META_PAGE + "?d=0&p=" + TARGET_PAGE + ); + await 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; + + let message = notification.messageText.querySelector("span"); + is( + message.dataset.l10nId, + "refresh-blocked-redirect-label", + "Should be showing the redirect message" + ); + + // Next, attempt a refresh + await attemptFakeRefresh(browser, false); + + message = notification.messageText.querySelector("span"); + is( + message.dataset.l10nId, + "refresh-blocked-refresh-label", + "Should be showing the refresh message" + ); + } + ); +}); 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 0000000000..22ed8fbb1b --- /dev/null +++ b/browser/base/content/test/general/browser_relatedTabs.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/. */ + +add_task(async 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 = []; + let ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" + ); + + function addTab(aURL, aReferrer) { + let referrerInfo = new ReferrerInfo( + Ci.nsIReferrerInfo.EMPTY, + true, + aReferrer + ); + let tab = BrowserTestUtils.addTab(gBrowser, aURL, { referrerInfo }); + tabs.push(tab); + return BrowserTestUtils.browserLoaded(tab.linkedBrowser); + } + + await addTab("http://mochi.test:8888/#0"); + gBrowser.selectedTab = tabs[0]; + await addTab("http://mochi.test:8888/#1"); + await addTab("http://mochi.test:8888/#2", gBrowser.currentURI); + await addTab("http://mochi.test:8888/#3", gBrowser.currentURI); + gBrowser.selectedTab = tabs[tabs.length - 1]; + gBrowser.selectedTab = tabs[0]; + await addTab("http://mochi.test:8888/#4", gBrowser.currentURI); + gBrowser.selectedTab = tabs[3]; + await addTab("http://mochi.test:8888/#5", gBrowser.currentURI); + gBrowser.removeTab(tabs.pop()); + await addTab("about:blank", gBrowser.currentURI); + gBrowser.moveTabTo(gBrowser.selectedTab, 1); + await addTab("http://mochi.test:8888/#6", gBrowser.currentURI); + await addTab(); + await addTab("http://mochi.test:8888/#7"); + + function testPosition(tabNum, expectedPosition, msg) { + is( + Array.prototype.indexOf.call(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 0000000000..84722b2603 --- /dev/null +++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js @@ -0,0 +1,130 @@ +/* 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 } = ChromeUtils.importESModule( + "resource://gre/modules/WebChannel.sys.mjs" +); +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +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); +const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URL_TAIL); +const TEST_URI_GOOD_OBJECT = Services.io.newURI( + "https://" + TEST_URL_TAIL + "?object" +); + +// 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.addTab(uri.spec, { + inBackground: false, + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + return promiseTabLoaded(tab) + .then(() => channelPromise) + .then(data => { + gBrowser.removeTab(tab); + return data; + }); +} + +add_task(async function () { + // We haven't set a permission yet - so even the "good" URI should fail. + let got = await promiseNewChannelResponse(TEST_URI_GOOD); + // Should return an error. + Assert.ok( + got.message.errno === 2, + "should have failed with errno 2, no such channel" + ); + + // Add a permission manager entry for our URI. + PermissionTestUtils.add( + TEST_URI_GOOD, + "remote-troubleshooting", + Services.perms.ALLOW_ACTION + ); + registerCleanupFunction(() => { + PermissionTestUtils.remove(TEST_URI_GOOD, "remote-troubleshooting"); + }); + + // Try again - now we are expecting a response with the actual data. + got = await promiseNewChannelResponse(TEST_URI_GOOD); + + // Check some keys we expect to always get. + Assert.ok(got.message.addons, "should have addons"); + 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 = ChromeUtils.importESModule( + "resource://gre/modules/UpdateUtils.sys.mjs" + ).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.printingPreferences, + "should not have a printingPreferences key" + ); + Assert.ok(!got.message.crashes, "should not have crash info"); + + // Now a http:// URI - should receive an error + got = await promiseNewChannelResponse(TEST_URI_BAD); + Assert.ok( + got.message.errno === 2, + "should have failed with errno 2, no such channel" + ); + + // 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 = await 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 0000000000..3ae7c62105 --- /dev/null +++ b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js @@ -0,0 +1,53 @@ +/* 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 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(async function test_remoteWebNavigation_postdata() { + let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); + let { CommonUtils } = ChromeUtils.importESModule( + "resource://services-common/utils.sys.mjs" + ); + + let server = new HttpServer(); + server.start(-1); + + await new Promise(resolve => { + server.registerPathHandler("/test", (request, response) => { + let body = 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); + 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"; + + openTrustedLinkIn(path, "tab", { + allowThirdPartyFixup: null, + postData: makeInputStream(postdata), + }); + }); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + await new Promise(resolve => { + server.stop(function () { + resolve(); + }); + }); +}); 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 0000000000..ab26342692 --- /dev/null +++ b/browser/base/content/test/general/browser_restore_isAppTab.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { TabStateFlusher } = ChromeUtils.importESModule( + "resource:///modules/sessionstore/TabStateFlusher.sys.mjs" +); + +const DUMMY = + "https://example.com/browser/browser/base/content/test/general/dummy_page.html"; + +function isBrowserAppTab(browser) { + return browser.browsingContext.isAppTab; +} + +// Restarts the child process by crashing it then reloading the tab +var restart = 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 + await TabStateFlusher.flush(browser); + + await BrowserTestUtils.crashFrame(browser); + + let tab = gBrowser.getTabForBrowser(browser); + SessionStore.reviveCrashedTab(tab); + + await promiseTabLoaded(tab); +}; + +add_task(async function navigate() { + let tab = BrowserTestUtils.addTab(gBrowser, "about:robots"); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + await BrowserTestUtils.browserStopped(gBrowser); + let isAppTab = isBrowserAppTab(browser); + ok(!isAppTab, "Docshell shouldn't think it is an app tab"); + + gBrowser.pinTab(tab); + isAppTab = isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + BrowserTestUtils.loadURIString(gBrowser, DUMMY); + await BrowserTestUtils.browserStopped(gBrowser); + isAppTab = isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.unpinTab(tab); + isAppTab = isBrowserAppTab(browser); + ok(!isAppTab, "Docshell shouldn't think it is an app tab"); + + gBrowser.pinTab(tab); + isAppTab = isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + BrowserTestUtils.loadURIString(gBrowser, "about:robots"); + await BrowserTestUtils.browserStopped(gBrowser); + isAppTab = isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.removeCurrentTab(); +}); + +add_task(async function crash() { + if (!gMultiProcessBrowser || !AppConstants.MOZ_CRASHREPORTER) { + return; + } + + let tab = BrowserTestUtils.addTab(gBrowser, DUMMY); + let browser = tab.linkedBrowser; + gBrowser.selectedTab = tab; + await BrowserTestUtils.browserStopped(gBrowser); + let isAppTab = isBrowserAppTab(browser); + ok(!isAppTab, "Docshell shouldn't think it is an app tab"); + + gBrowser.pinTab(tab); + isAppTab = isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + await restart(browser); + isAppTab = isBrowserAppTab(browser); + ok(isAppTab, "Docshell should think it is an app tab"); + + gBrowser.removeCurrentTab(); +}); 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 0000000000..4800c813b3 --- /dev/null +++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js @@ -0,0 +1,214 @@ +/* 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"; + BrowserTestUtils.loadURIString(testBrowser, testURI); + BrowserTestUtils.browserLoaded(testBrowser, false, testURI).then(() => { + waitForFocus(function () { + info("register to handle popupshown"); + aWindow.document.addEventListener("popupshown", contextMenuOpened); + + 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.setFiles([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"); + } + + 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 new Error("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"); + Services.obs.addObserver(observer, "http-on-examine-response"); + + 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(); + }); + }); + }); + }); +} + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +function createTemporarySaveDirectory() { + var saveDir = Services.dirsvc.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 0000000000..49901e8bfa --- /dev/null +++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js @@ -0,0 +1,197 @@ +/* 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 ALWAYS_ASK_PREF = "browser.download.always_ask_before_handling_new_types"; +const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xhtml"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +function createTemporarySaveDirectory() { + var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + info("create testsavedir!"); + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", saveDir); + 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"; + + // Only observe the UTC dialog if it's enabled by pref + if (Services.prefs.getBoolPref(ALWAYS_ASK_PREF)) { + windowObserver.setCallback(onUCTDialog); + } + + BrowserTestUtils.loadURIString(testBrowser, 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.setFiles([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) { + SpecialPowers.spawn(testBrowser, [], async () => { + content.document.querySelector("iframe").remove(); + }).then(() => executeSoon(continueDownloading)); + } + + function continueDownloading() { + for (let win of Services.wm.getEnumerator("")) { + if (win.location && win.location.href == UCT_URI) { + win.document + .getElementById("unknownContentType") + ._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(aCallback) { + if (this._callback) { + ok(false, "Should only be dealing with one callback at a time."); + } + this._callback = aCallback; + }, + observe(aSubject, aTopic, aData) { + if (aTopic != "domwindowopened") { + return; + } + + let win = aSubject; + + win.addEventListener( + "load", + function (event) { + if (win.location == UCT_URI) { + SimpleTest.executeSoon(function () { + if (windowObserver._callback) { + windowObserver._callback(win); + delete windowObserver._callback; + } else { + ok(false, "Unexpected UCT dialog!"); + } + }); + } + }, + { once: true } + ); + }, +}; + +Services.ww.registerNotification(windowObserver); + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref(ALWAYS_ASK_PREF, false); + + 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"); + } + + 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); + Services.prefs.clearUserPref(ALWAYS_ASK_PREF); + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + info("Finished running the cleanup code"); + }); + + info( + `Running test with ${ALWAYS_ASK_PREF} set to ${Services.prefs.getBoolPref( + ALWAYS_ASK_PREF, + false + )}` + ); + testOnWindow(undefined, function (win) { + let windowGonePromise = BrowserTestUtils.domWindowClosed(win); + Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true); + triggerSave(win, async function () { + await windowGonePromise; + 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 0000000000..8ede97e640 --- /dev/null +++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js @@ -0,0 +1,127 @@ +/* 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 = Services.dirsvc.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(num, consumption) { + info("disk storage contains " + num + " entries"); + }, + onCacheEntryInfo(uri) { + let urispec = uri.asciiSpec; + info(urispec); + is( + urispec.includes(filename), + false, + "web content present in disk cache" + ); + }, + onCacheEntryVisitCompleted() { + resolve(); + }, + }; + function Visitor() {} + + let storage = Services.cache2.diskCacheStorage( + Services.loadContextInfo.default + ); + 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.setFiles([destFile]); + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; + + mockTransferCallback = onTransferComplete; + mockTransferRegisterer.register(); + + registerCleanupFunction(function () { + mockTransferCallback = null; + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + destDir.remove(true); + }); + }); +} + +add_task(async function () { + let testURI = + "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html"; + let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + let tab = await BrowserTestUtils.openNewForegroundTab( + privateWindow.gBrowser, + testURI + ); + + let contextMenu = privateWindow.document.getElementById( + "contentAreaContextMenu" + ); + let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#img", + { + type: "contextmenu", + button: 2, + }, + tab.linkedBrowser + ); + await popupShown; + + Services.cache2.clear(); + + let imageDownloaded = promiseImageDownloaded(); + // Select "Save Image As" option from context menu + privateWindow.document.getElementById("context-saveimage").doCommand(); + + contextMenu.hidePopup(); + await popupHidden; + + // wait for image download + let fileName = await imageDownloaded; + await promiseNoCacheEntry(fileName); + + await BrowserTestUtils.closeWindow(privateWindow); +}); + +Services.scriptloader.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 0000000000..5456ac240f --- /dev/null +++ b/browser/base/content/test/general/browser_save_video.js @@ -0,0 +1,99 @@ +/* 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(async function () { + var fileName; + + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString( + gBrowser, + "http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html" + ); + await loadPromise; + + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#video1", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + info("context menu click on video1"); + + await 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.setFiles([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(); + await popupHiddenPromise; + + await transferCompletePromise; +}); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +function createTemporarySaveDirectory() { + var saveDir = Services.dirsvc.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 0000000000..877c33bcd3 --- /dev/null +++ b/browser/base/content/test/general/browser_save_video_frame.js @@ -0,0 +1,103 @@ +/* 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. + */ +Services.scriptloader.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 = Services.dirsvc.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"); + mockTransferCallback = () => {}; + resolve(); + }; + }); +} + +/** + * 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(async 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.setFiles([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 = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + info("Loading video tab"); + await promiseTabLoadEvent(tab, VIDEO_URL); + info("Video tab loaded."); + + let context = document.getElementById("contentAreaContextMenu"); + let popupPromise = promisePopupShown(context); + + info("Synthesizing right-click on video element"); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#video1", + { type: "contextmenu", button: 2 }, + browser + ); + info("Waiting for popup to fire popupshown."); + await 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"); + await promiseTransfer; + info("Transfer complete"); + gBrowser.removeTab(tab); +}); 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 0000000000..5d2e8c739e --- /dev/null +++ b/browser/base/content/test/general/browser_selectTabAtIndex.js @@ -0,0 +1,89 @@ +"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++) { + BrowserTestUtils.addTab(gBrowser); + } + } + + 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_star_hsts.js b/browser/base/content/test/general/browser_star_hsts.js new file mode 100644 index 0000000000..9452c61beb --- /dev/null +++ b/browser/base/content/test/general/browser_star_hsts.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +var secureURL = + "https://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs"; +var unsecureURL = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs"; + +add_task(async function test_star_redirect() { + registerCleanupFunction(async () => { + // Ensure to remove example.com from the HSTS list. + let sss = Cc["@mozilla.org/ssservice;1"].getService( + Ci.nsISiteSecurityService + ); + sss.resetState( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + NetUtil.newURI("http://example.com/"), + Services.prefs.getBoolPref("privacy.partition.network_state") + ? { partitionKey: "(http,example.com)" } + : {} + ); + await PlacesUtils.bookmarks.eraseEverything(); + gBrowser.removeCurrentTab(); + }); + + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + // This will add the page to the HSTS cache. + await promiseTabLoadEvent(tab, secureURL, secureURL); + // This should transparently be redirected to the secure page. + await promiseTabLoadEvent(tab, unsecureURL, secureURL); + + await promiseStarState(BookmarkingUI.STATUS_UNSTARRED); + + StarUI._createPanelIfNeeded(); + let bookmarkPanel = document.getElementById("editBookmarkPanel"); + let shownPromise = promisePopupShown(bookmarkPanel); + BookmarkingUI.star.click(); + await shownPromise; + + is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred"); +}); + +/** + * Waits for the star to reflect the expected state. + */ +function promiseStarState(aValue) { + return new Promise(resolve => { + 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 { + resolve(); + } + })(); + }); +} + +/** + * 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; + } + + info("Wait for load tab event"); + BrowserTestUtils.loadURIString(aTab.linkedBrowser, aURL); + return BrowserTestUtils.browserLoaded(aTab.linkedBrowser, false, aFinalURL); +} 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 0000000000..64c4235288 --- /dev/null +++ b/browser/base/content/test/general/browser_star_hsts.sjs @@ -0,0 +1,12 @@ +/* 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_storagePressure_notification.js b/browser/base/content/test/general/browser_storagePressure_notification.js new file mode 100644 index 0000000000..dcafbe8bf9 --- /dev/null +++ b/browser/base/content/test/general/browser_storagePressure_notification.js @@ -0,0 +1,182 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +async function notifyStoragePressure(usage = 100) { + let notifyPromise = TestUtils.topicObserved( + "QuotaManager::StoragePressure", + () => true + ); + let usageWrapper = Cc["@mozilla.org/supports-PRUint64;1"].createInstance( + Ci.nsISupportsPRUint64 + ); + usageWrapper.data = usage; + Services.obs.notifyObservers(usageWrapper, "QuotaManager::StoragePressure"); + return notifyPromise; +} + +function openAboutPrefPromise(win) { + let promises = [ + BrowserTestUtils.waitForLocationChange( + win.gBrowser, + "about:preferences#privacy" + ), + TestUtils.topicObserved("privacy-pane-loaded", () => true), + TestUtils.topicObserved("sync-pane-loaded", () => true), + ]; + return Promise.all(promises); +} +add_setup(async function () { + let win = await BrowserTestUtils.openNewBrowserWindow(); + // Open a new tab to keep the window open. + await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + "https://example.com" + ); +}); + +// Test only displaying notification once within the given interval +add_task(async function () { + const win = Services.wm.getMostRecentWindow("navigator:browser"); + const TEST_NOTIFICATION_INTERVAL_MS = 2000; + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.storageManager.pressureNotification.minIntervalMS", + TEST_NOTIFICATION_INTERVAL_MS, + ], + ], + }); + // Commenting this to see if we really need it + // await SpecialPowers.pushPrefEnv({set: [["privacy.reduceTimerPrecision", false]]}); + + await notifyStoragePressure(); + await TestUtils.waitForCondition(() => + win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ) + ); + let notification = win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ); + is( + notification.localName, + "notification-message", + "Should display storage pressure notification" + ); + notification.close(); + + await notifyStoragePressure(); + notification = win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ); + is( + notification, + null, + "Should not display storage pressure notification more than once within the given interval" + ); + + await new Promise(resolve => + setTimeout(resolve, TEST_NOTIFICATION_INTERVAL_MS + 1) + ); + await notifyStoragePressure(); + await TestUtils.waitForCondition(() => + win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ) + ); + notification = win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ); + is( + notification.localName, + "notification-message", + "Should display storage pressure notification after the given interval" + ); + notification.close(); +}); + +// Test guiding user to the about:preferences when usage exceeds the given threshold +add_task(async function () { + const win = Services.wm.getMostRecentWindow("navigator:browser"); + await SpecialPowers.pushPrefEnv({ + set: [["browser.storageManager.pressureNotification.minIntervalMS", 0]], + }); + let tab = await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + "https://example.com" + ); + + const BYTES_IN_GIGABYTE = 1073741824; + const USAGE_THRESHOLD_BYTES = + BYTES_IN_GIGABYTE * + Services.prefs.getIntPref( + "browser.storageManager.pressureNotification.usageThresholdGB" + ); + await notifyStoragePressure(USAGE_THRESHOLD_BYTES); + await TestUtils.waitForCondition(() => + win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ) + ); + let notification = win.gNotificationBox.getNotificationWithValue( + "storage-pressure-notification" + ); + is( + notification.localName, + "notification-message", + "Should display storage pressure notification" + ); + await new Promise(r => setTimeout(r, 1000)); + + let prefBtn = notification.buttonContainer.getElementsByTagName("button")[0]; + ok(prefBtn, "Should have an open preferences button"); + let aboutPrefPromise = openAboutPrefPromise(win); + EventUtils.synthesizeMouseAtCenter(prefBtn, {}, win); + await aboutPrefPromise; + let aboutPrefTab = win.gBrowser.selectedTab; + let prefDoc = win.gBrowser.selectedBrowser.contentDocument; + let siteDataGroup = prefDoc.getElementById("siteDataGroup"); + is_element_visible( + siteDataGroup, + "Should open to the siteDataGroup section in about:preferences" + ); + BrowserTestUtils.removeTab(aboutPrefTab); + BrowserTestUtils.removeTab(tab); +}); + +// Test not displaying the 2nd notification if one is already being displayed +add_task(async function () { + const win = Services.wm.getMostRecentWindow("navigator:browser"); + const TEST_NOTIFICATION_INTERVAL_MS = 0; + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.storageManager.pressureNotification.minIntervalMS", + TEST_NOTIFICATION_INTERVAL_MS, + ], + ], + }); + + await notifyStoragePressure(); + await notifyStoragePressure(); + let allNotifications = win.gNotificationBox.allNotifications; + let pressureNotificationCount = 0; + allNotifications.forEach(notification => { + if (notification.getAttribute("value") == "storage-pressure-notification") { + pressureNotificationCount++; + } + }); + is( + pressureNotificationCount, + 1, + "Should not display the 2nd notification when there is already one" + ); + win.gNotificationBox.removeAllNotifications(); +}); + +add_task(async function cleanup() { + const win = Services.wm.getMostRecentWindow("navigator:browser"); + await BrowserTestUtils.closeWindow(win); +}); 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 0000000000..eddb405f46 --- /dev/null +++ b/browser/base/content/test/general/browser_tabDrop.js @@ -0,0 +1,207 @@ +// TODO (Bug 1680996): Investigate why this test takes a long time. +requestLongerTimeout(2); + +const ANY_URL = undefined; + +const { SearchTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SearchTestUtils.sys.mjs" +); + +SearchTestUtils.init(this); + +registerCleanupFunction(async function cleanup() { + while (gBrowser.tabs.length > 1) { + BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + } +}); + +add_task(async function test_setup() { + // Stop search-engine loads from hitting the network + await SearchTestUtils.installSearchExtension( + { + name: "MozSearch", + search_url: "https://example.com/", + search_url_get_params: "q={searchTerms}", + }, + { setAsDefault: true } + ); +}); + +add_task(async function single_url() { + await dropText("mochi.test/first", ["http://mochi.test/first"]); +}); +add_task(async function single_javascript() { + await dropText("javascript:'bad'", []); +}); +add_task(async function single_javascript_capital() { + await dropText("jAvascript:'bad'", []); +}); +add_task(async function single_search() { + await dropText("search this", [ANY_URL]); +}); +add_task(async function single_url2() { + await dropText("mochi.test/second", ["http://mochi.test/second"]); +}); +add_task(async function single_data_url() { + await dropText("data:text/html,bad", []); +}); +add_task(async function single_url3() { + await dropText("mochi.test/third", ["http://mochi.test/third"]); +}); + +// Single text/plain item, with multiple links. +add_task(async function multiple_urls() { + await dropText("mochi.test/1\nmochi.test/2", [ + "http://mochi.test/1", + "http://mochi.test/2", + ]); +}); +add_task(async function multiple_urls_javascript() { + await dropText("javascript:'bad1'\nmochi.test/3", []); +}); +add_task(async function multiple_urls_data() { + await dropText("mochi.test/4\ndata:text/html,bad1", []); +}); + +// Multiple text/plain items, with single and multiple links. +add_task(async function multiple_items_single_and_multiple_links() { + await drop( + [ + [{ type: "text/plain", data: "mochi.test/5" }], + [{ type: "text/plain", data: "mochi.test/6\nmochi.test/7" }], + ], + ["http://mochi.test/5", "http://mochi.test/6", "http://mochi.test/7"] + ); +}); + +// Single text/x-moz-url item, with multiple links. +// "text/x-moz-url" has titles in even-numbered lines. +add_task(async function single_moz_url_multiple_links() { + await drop( + [ + [ + { + type: "text/x-moz-url", + data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9", + }, + ], + ], + ["http://mochi.test/8", "http://mochi.test/9"] + ); +}); + +// Single item with multiple types. +add_task(async function single_item_multiple_types() { + await drop( + [ + [ + { type: "text/plain", data: "mochi.test/10" }, + { type: "text/x-moz-url", data: "mochi.test/11\nTITLE11" }, + ], + ], + ["http://mochi.test/11"] + ); +}); + +// Warn when too many URLs are dropped. +add_task(async function multiple_tabs_under_max() { + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/multi" + i); + } + await dropText(urls.join("\n"), [ + "http://mochi.test/multi0", + "http://mochi.test/multi1", + "http://mochi.test/multi2", + "http://mochi.test/multi3", + "http://mochi.test/multi4", + ]); +}); +add_task(async function multiple_tabs_over_max_accept() { + await pushPrefs(["browser.tabs.maxOpenBeforeWarn", 4]); + + let confirmPromise = BrowserTestUtils.promiseAlertDialog("accept"); + + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/accept" + i); + } + await dropText(urls.join("\n"), [ + "http://mochi.test/accept0", + "http://mochi.test/accept1", + "http://mochi.test/accept2", + "http://mochi.test/accept3", + "http://mochi.test/accept4", + ]); + + await confirmPromise; + + await popPrefs(); +}); +add_task(async function multiple_tabs_over_max_cancel() { + await pushPrefs(["browser.tabs.maxOpenBeforeWarn", 4]); + + let confirmPromise = BrowserTestUtils.promiseAlertDialog("cancel"); + + let urls = []; + for (let i = 0; i < 5; i++) { + urls.push("mochi.test/cancel" + i); + } + await dropText(urls.join("\n"), []); + + await confirmPromise; + + await popPrefs(); +}); + +function dropText(text, expectedURLs) { + return drop([[{ type: "text/plain", data: text }]], expectedURLs); +} + +async function drop(dragData, expectedURLs) { + let dragDataString = JSON.stringify(dragData); + info( + `Starting test for dragData:${dragDataString}; expectedURLs.length:${expectedURLs.length}` + ); + let EventUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils + ); + + let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop"); + + let loadedPromises = expectedURLs.map(url => + BrowserTestUtils.waitForNewTab(gBrowser, url, false, true) + ); + + // 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 tabs = await Promise.all(loadedPromises); + for (let tab of tabs) { + BrowserTestUtils.removeTab(tab); + } + + await awaitDrop; + ok(true, "Got drop event"); +} 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 0000000000..a9b9c1d999 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_close_dependent_window.js @@ -0,0 +1,35 @@ +"use strict"; + +add_task(async function closing_tab_with_dependents_should_close_window() { + info("Opening window"); + let win = await BrowserTestUtils.openNewBrowserWindow(); + + info("Opening tab with data URI"); + let tab = await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + `data:text/html,<html%20onclick="W=window.open()"><body%20onbeforeunload="W.close()">` + ); + info("Closing original tab in this window."); + BrowserTestUtils.removeTab(win.gBrowser.tabs[0]); + info("Clicking into the window"); + let depTabOpened = BrowserTestUtils.waitForEvent( + win.gBrowser.tabContainer, + "TabOpen" + ); + await BrowserTestUtils.synthesizeMouse("html", 0, 0, {}, tab.linkedBrowser); + + let openedTab = (await depTabOpened).target; + info("Got opened tab"); + + let windowClosedPromise = BrowserTestUtils.windowClosed(win); + 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!" + ); + await 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 0000000000..d3f6a58aaa --- /dev/null +++ b/browser/base/content/test/general/browser_tab_detach_restore.js @@ -0,0 +1,54 @@ +"use strict"; + +const { TabStateFlusher } = ChromeUtils.importESModule( + "resource:///modules/sessionstore/TabStateFlusher.sys.mjs" +); + +add_task(async function () { + let uri = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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 = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.loadURIString(tab.linkedBrowser, uri); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, uri); + await TabStateFlusher.flush(tab.linkedBrowser); + + let key = tab.linkedBrowser.permanentKey; + let win = gBrowser.replaceTabWithWindow(tab); + await new Promise(resolve => whenDelayedStartupFinished(win, resolve)); + + is( + win.gBrowser.selectedBrowser.permanentKey, + key, + "Should have properly copied the permanentKey" + ); + await BrowserTestUtils.closeWindow(win); + + is( + SessionStore.getClosedWindowCount(), + 1, + "Should have restore data for the closed window" + ); + + win = SessionStore.undoCloseWindow(0); + await BrowserTestUtils.waitForEvent(win, "load"); + await BrowserTestUtils.waitForEvent( + win.gBrowser.tabContainer, + "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" + ); + + await 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 0000000000..de4e17b97d --- /dev/null +++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js @@ -0,0 +1,423 @@ +/* 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(async function test_dragging_private_windows() { + let normalWin = await BrowserTestUtils.openNewBrowserWindow(); + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + + let normalTab = await BrowserTestUtils.openNewForegroundTab( + normalWin.gBrowser + ); + let privateTab = await 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" + ); + + await BrowserTestUtils.closeWindow(normalWin); + await BrowserTestUtils.closeWindow(privateWin); +}); + +/** + * Tests that tabs from e10s windows cannot be dragged into non-e10s + * windows, and vice-versa. + */ +add_task(async function test_dragging_e10s_windows() { + if (!gMultiProcessBrowser) { + return; + } + + let remoteWin = await BrowserTestUtils.openNewBrowserWindow({ remote: true }); + let nonRemoteWin = await BrowserTestUtils.openNewBrowserWindow({ + remote: false, + fission: false, + }); + + let remoteTab = await BrowserTestUtils.openNewForegroundTab( + remoteWin.gBrowser + ); + let nonRemoteTab = await 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" + ); + + await BrowserTestUtils.closeWindow(remoteWin); + await BrowserTestUtils.closeWindow(nonRemoteWin); +}); + +/** + * Tests that tabs from fission windows cannot be dragged into non-fission + * windows, and vice-versa. + */ +add_task(async function test_dragging_fission_windows() { + let fissionWin = await BrowserTestUtils.openNewBrowserWindow({ + remote: true, + fission: true, + }); + let nonFissionWin = await BrowserTestUtils.openNewBrowserWindow({ + remote: true, + fission: false, + }); + + let fissionTab = await BrowserTestUtils.openNewForegroundTab( + fissionWin.gBrowser + ); + let nonFissionTab = await BrowserTestUtils.openNewForegroundTab( + nonFissionWin.gBrowser + ); + + let effect = EventUtils.synthesizeDrop( + fissionTab, + nonFissionTab, + [[{ type: TAB_DROP_TYPE, data: fissionTab }]], + null, + fissionWin, + nonFissionWin + ); + is( + effect, + "none", + "Should not be able to drag a fission tab to a non-fission window" + ); + + effect = EventUtils.synthesizeDrop( + nonFissionTab, + fissionTab, + [[{ type: TAB_DROP_TYPE, data: nonFissionTab }]], + null, + nonFissionWin, + fissionWin + ); + is( + effect, + "none", + "Should not be able to drag a non-fission tab to an fission window" + ); + + let swapOk = fissionWin.gBrowser.swapBrowsersAndCloseOther( + fissionTab, + nonFissionTab + ); + is( + swapOk, + false, + "Returns false swapping fission tab to a non-fission tabbrowser" + ); + is( + fissionWin.gBrowser.tabs.length, + 2, + "Prevent moving a fission tab to a non-fission tabbrowser" + ); + is( + nonFissionWin.gBrowser.tabs.length, + 2, + "Prevent accepting a fission tab in a non-fission tabbrowser" + ); + + swapOk = nonFissionWin.gBrowser.swapBrowsersAndCloseOther( + nonFissionTab, + fissionTab + ); + is( + swapOk, + false, + "Returns false swapping non-fission tab to a fission tabbrowser" + ); + is( + nonFissionWin.gBrowser.tabs.length, + 2, + "Prevent moving a non-fission tab to a fission tabbrowser" + ); + is( + fissionWin.gBrowser.tabs.length, + 2, + "Prevent accepting a non-fission tab in a fission tabbrowser" + ); + + await BrowserTestUtils.closeWindow(fissionWin); + await BrowserTestUtils.closeWindow(nonFissionWin); +}); + +/** + * Tests that remoteness-blacklisted tabs from e10s windows can + * be dragged between e10s windows. + */ +add_task(async function test_dragging_blacklisted() { + if (!gMultiProcessBrowser) { + return; + } + + let remoteWin1 = await BrowserTestUtils.openNewBrowserWindow({ + remote: true, + }); + remoteWin1.gBrowser.myID = "remoteWin1"; + let remoteWin2 = await 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 = await 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 = await 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}` + ); + + await BrowserTestUtils.closeWindow(remoteWin1); + await BrowserTestUtils.closeWindow(remoteWin2); +}); + +/** + * Tests that tabs dragged between windows dispatch TabOpen and TabClose + * events with the appropriate adoption details. + */ +add_task(async function test_dragging_adoption_events() { + let win1 = await BrowserTestUtils.openNewBrowserWindow(); + let win2 = await BrowserTestUtils.openNewBrowserWindow(); + + let tab1 = await BrowserTestUtils.openNewForegroundTab(win1.gBrowser); + let tab2 = await 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 = await awaitCloseEvent; + let openEvent = await awaitOpenEvent; + + is(openEvent.detail.adoptedTab, tab1, "New tab adopted old tab"); + is( + closeEvent.detail.adoptedBy, + openEvent.target, + "Old tab adopted by new tab" + ); + + await BrowserTestUtils.closeWindow(win1); + await BrowserTestUtils.closeWindow(win2); +}); + +/** + * Tests that per-site zoom settings remain active after a tab is + * dragged between windows. + */ +add_task(async function test_dragging_zoom_handling() { + const ZOOM_FACTOR = 1.62; + + let win1 = await BrowserTestUtils.openNewBrowserWindow(); + let win2 = await BrowserTestUtils.openNewBrowserWindow(); + + let tab1 = await BrowserTestUtils.openNewForegroundTab(win1.gBrowser); + let tab2 = await BrowserTestUtils.openNewForegroundTab( + win2.gBrowser, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/" + ); + + win2.FullZoom.setZoom(ZOOM_FACTOR); + is( + ZoomManager.getZoomForBrowser(tab2.linkedBrowser), + 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. + await new Promise(resolve => setTimeout(resolve, 0)); + + is( + ZoomManager.getZoomForBrowser(win1.gBrowser.selectedBrowser), + ZOOM_FACTOR, + "Dragged tab should have correct zoom factor" + ); + + win1.FullZoom.reset(); + + await BrowserTestUtils.closeWindow(win1); + await 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 0000000000..9ea05842f2 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop.js @@ -0,0 +1,257 @@ +// Swaps the content of tab a into tab b and then closes tab a. +function swapTabsAndCloseOther(a, b) { + gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]); +} + +// Mirrors the effect of the above function on an array. +function swapArrayContentsAndRemoveOther(arr, a, b) { + arr[b] = arr[a]; + arr.splice(a, 1); +} + +function checkBrowserIds(expected) { + is( + gBrowser.tabs.length, + expected.length, + "Should have the right number of tabs." + ); + + for (let [i, tab] of gBrowser.tabs.entries()) { + is( + tab.linkedBrowser.browserId, + expected[i], + `Tab ${i} should have the right browser ID.` + ); + is( + tab.linkedBrowser.browserId, + tab.linkedBrowser.browsingContext.browserId, + `Browser for tab ${i} has the same browserId as its BrowsingContext` + ); + } +} + +var getClicks = function (tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], function () { + return content.wrappedJSObject.clicks; + }); +}; + +var clickTest = async function (tab) { + let clicks = await getClicks(tab); + + await SpecialPowers.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.windowUtils; + utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0); + }); + + let newClicks = await getClicks(tab); + is(newClicks, clicks + 1, "adding 1 more click on BODY"); +}; + +function loadURI(tab, url) { + BrowserTestUtils.loadURIString(tab.linkedBrowser, 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. +async function cacheObjectValue(browser) { + await SpecialPowers.spawn(browser, [], () => { + let plugin = content.document.getElementById("p").wrappedJSObject; + info(`plugin is ${plugin}`); + let win = content.document.defaultView; + info(`win is ${win}`); + win.objectValue = plugin.getObjectValue(); + info(`got objectValue: ${win.objectValue}`); + }); +} + +// Note, can't run this via registerCleanupFunction because it needs the +// browser to still be alive and have a messageManager. +async function cleanupObjectValue(browser) { + info("entered cleanupObjectValue"); + await SpecialPowers.spawn(browser, [], () => { + info("in cleanup function"); + let win = content.document.defaultView; + info(`about to delete objectValue: ${win.objectValue}`); + delete win.objectValue; + }); + info("exiting cleanupObjectValue"); +} + +// See the notes for cacheObjectValue above. +async function checkObjectValue(browser) { + let data = await SpecialPowers.spawn(browser, [], () => { + let plugin = content.document.getElementById("p").wrappedJSObject; + let win = content.document.defaultView; + let result, exception; + try { + result = plugin.checkObjectValue(win.objectValue); + } catch (e) { + exception = e.toString(); + } + return { + result, + exception, + }; + }); + + if (data.result === null) { + ok(false, "checkObjectValue threw an exception: " + data.exception); + throw new Error(data.exception); + } else { + return data.result; + } +} + +add_task(async function () { + // create a few tabs + let tabs = [ + gBrowser.tabs[0], + BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true }), + BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true }), + BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true }), + BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true }), + ]; + + // Initially 0 1 2 3 4 + await loadURI( + tabs[1], + "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>" + ); + await loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2"); + await loadURI( + tabs[3], + "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>" + ); + await loadURI( + tabs[4], + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/general/browser_tab_dragdrop_embed.html" + ); + await BrowserTestUtils.switchTab(gBrowser, tabs[3]); + + let browserIds = tabs.map(t => t.linkedBrowser.browserId); + checkBrowserIds(browserIds); + + is(gBrowser.tabs[1], tabs[1], "tab1"); + is(gBrowser.tabs[2], tabs[2], "tab2"); + is(gBrowser.tabs[3], tabs[3], "tab3"); + is(gBrowser.tabs[4], tabs[4], "tab4"); + + swapTabsAndCloseOther(2, 3); // now: 0 1 2 4 + // Tab 2 is gone (what was tab 3 is displaying its content). + tabs.splice(2, 1); + swapArrayContentsAndRemoveOther(browserIds, 2, 3); + + is(gBrowser.tabs[1], tabs[1], "tab1"); + is(gBrowser.tabs[2], tabs[2], "tab2"); + is(gBrowser.tabs[3], tabs[3], "tab4"); + + checkBrowserIds(browserIds); + + info("about to cacheObjectValue"); + await cacheObjectValue(tabs[3].linkedBrowser); + info("just finished cacheObjectValue"); + + swapTabsAndCloseOther(3, 2); // now: 0 1 4 + tabs.splice(3, 1); + swapArrayContentsAndRemoveOther(browserIds, 3, 2); + + is( + Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), + 2, + "The third tab should be selected" + ); + + checkBrowserIds(browserIds); + + ok( + await checkObjectValue(gBrowser.tabs[2].linkedBrowser), + "same plugin instance" + ); + + is(gBrowser.tabs[1], tabs[1], "tab1"); + is(gBrowser.tabs[2], tabs[2], "tab4"); + + let clicks = await getClicks(gBrowser.tabs[2]); + is(clicks, 0, "no click on BODY so far"); + await clickTest(gBrowser.tabs[2]); + + swapTabsAndCloseOther(2, 1); // now: 0 4 + tabs.splice(2, 1); + swapArrayContentsAndRemoveOther(browserIds, 2, 1); + + is(gBrowser.tabs[1], tabs[1], "tab4"); + + checkBrowserIds(browserIds); + + ok( + await checkObjectValue(gBrowser.tabs[1].linkedBrowser), + "same plugin instance" + ); + await cleanupObjectValue(gBrowser.tabs[1].linkedBrowser); + + await 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" + ); + await loadURI(tabs[1], "about:blank"); + let key = tabs[1].linkedBrowser.permanentKey; + + checkBrowserIds(browserIds); + + let win = gBrowser.replaceTabWithWindow(tabs[1]); + await new Promise(resolve => whenDelayedStartupFinished(win, resolve)); + + let newWinBrowserId = browserIds[1]; + browserIds.splice(1, 1); + checkBrowserIds(browserIds); + + // 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"); + is(tab.linkedBrowser.browserId, newWinBrowserId, "Should have kept the ID"); + is( + tab.linkedBrowser.browserId, + tab.linkedBrowser.browsingContext.browserId, + "Should have kept the ID" + ); + + let awaitPageShow = BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "pageshow" + ); + win.gBrowser.goBack(); + await awaitPageShow; + + await 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 0000000000..9c589922f5 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop2.js @@ -0,0 +1,65 @@ +"use strict"; + +const ROOT = getRootDirectory(gTestPath); +const URI = ROOT + "browser_tab_dragdrop2_frame1.xhtml"; + +// 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(async function () { + // Open a new window. + let args = "chrome,all,dialog=no"; + let win = window.openDialog( + AppConstants.BROWSER_CHROME_URL, + "_blank", + args, + URI + ); + + // Wait until the tests were run. + await promiseTestsDone(win); + ok(true, "tests succeeded"); + + // Create a second tab so that we can move the original one out. + BrowserTestUtils.addTab(win.gBrowser, "about:blank", { skipAnimation: true }); + + // Tear off the original tab. + let browser = win.gBrowser.selectedBrowser; + let tabClosed = BrowserTestUtils.waitForEvent(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. + await 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); + let browser2 = win2.gBrowser.selectedBrowser; + await SpecialPowers.spawn(browser2, [], async () => { + content.test_panels(); + }); + await promise; + ok(true, "tests succeeded a second time"); + + // Cleanup. + await promiseWindowClosed(win2); + await promiseWindowClosed(win); +}); + +function promiseTestsDone(win) { + return BrowserTestUtils.waitForEvent(win, "TestsDone"); +} + +function promiseDelayedStartupFinished(win) { + return new Promise(resolve => whenDelayedStartupFinished(win, resolve)); +} diff --git a/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xhtml b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xhtml new file mode 100644 index 0000000000..d64f37c289 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xhtml @@ -0,0 +1,158 @@ +<?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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script 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.includes("Linux") && + panel.screenY == 210) { + waitSteps--; + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + setTimeout(popupShown, 10, event); + return; + } + ++i; + + currentTest.result(currentTest.testname + " ", panel); + panel.hidePopup(); +} + +function createPanel(attrs) +{ + var panel = document.createXULElement("panel"); + for (var a in attrs) { + panel.setAttribute(a, attrs[a]); + } + + var button = document.createXULElement("button"); + panel.appendChild(button); + button.label = "OK"; + button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0; height: 40px; width: 120px;"); + 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.scrollToRow(0); + synthesizeMouse(treechildren, 10, tree.rowHeight + 2, { }); + + tree.scrollToRow(2); + synthesizeMouse(treechildren, 10, tree.rowHeight + 2, { }); +} + +var tests = [ + { + testname: "normal panel", + attrs: { }, + test(panel) { + panel.openPopupAtScreen(200, 210); + }, + result(testname, panel) { + if (my_debug) alert(testname); + panel.getBoundingClientRect(); + } + }, + { + // only noautohide panels support titlebars, so one shouldn't be shown here + testname: "autohide panel with titlebar", + attrs: { titlebar: "normal" }, + test(panel) { + panel.openPopupAtScreen(200, 210); + }, + result(testname, panel) { + if (my_debug) alert(testname); + panel.getBoundingClientRect(); + } + }, + { + testname: "noautohide panel with titlebar", + attrs: { noautohide: true, titlebar: "normal" }, + test(panel) { + waitSteps = 25; + panel.openPopupAtScreen(200, 210); + }, + result(testname, panel) { + if (my_debug) alert(testname); + panel.getBoundingClientRect(); + + synthesizeMouse(panel, 10, 10, { type: "mousemove" }); + + 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_tab_dragdrop_embed.html b/browser/base/content/test/general/browser_tab_dragdrop_embed.html new file mode 100644 index 0000000000..bad0650693 --- /dev/null +++ b/browser/base/content/test/general/browser_tab_dragdrop_embed.html @@ -0,0 +1,2 @@ +<body onload="clicks=0" onclick="++clicks"> + <embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480" id="p"></embed> 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 0000000000..b057a504e5 --- /dev/null +++ b/browser/base/content/test/general/browser_tabfocus.js @@ -0,0 +1,811 @@ +/* + * 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(event) { + if (event.includes("browser1") || event.includes("browser2")) { + this["main-window"].push(event); + } else if (event.includes("1")) { + this.window1.push(event); + } else if (event.includes("2")) { + 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) { + return SpecialPowers.spawn( + browser, + [dontCheckExtraFocus], + dontCheckExtraFocusChild => { + let focusedWindow = {}; + let node = Services.focus.getFocusedElementForWindow( + content, + false, + focusedWindow + ); + let 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 (!dontCheckExtraFocusChild) { + if (Services.focus.focusedElement != node) { + details += "<ERROR: focusedElement doesn't match>"; + } + if ( + Services.focus.focusedWindow && + Services.focus.focusedWindow != content + ) { + details += "<ERROR: focusedWindow doesn't match>"; + } + if ((Services.focus.focusedWindow == content) != doc.hasFocus()) { + details += "<ERROR: child hasFocus() is not correct>"; + } + if ( + (Services.focus.focusedElement && + doc.activeElement != Services.focus.focusedElement) || + (!Services.focus.focusedElement && doc.activeElement != doc.body) + ) { + details += "<ERROR: child activeElement is not correct>"; + } + } + return details; + } + ); +} + +function focusInChild(event) { + function getWindowDocId(target) { + return String(target.location).includes("1") ? "window1" : "window2"; + } + + // Stop the shim code from seeing this event process. + event.stopImmediatePropagation(); + + var id; + if (event.target instanceof Ci.nsIDOMWindow) { + id = getWindowDocId(event.originalTarget) + "-window"; + } else if (event.target.nodeType == event.target.DOCUMENT_NODE) { + id = getWindowDocId(event.originalTarget) + "-document"; + } else { + id = event.originalTarget.id; + } + + let window = event.target.ownerGlobal; + if (!window._eventsOccurred) { + window._eventsOccurred = []; + } + window._eventsOccurred.push(event.type + ": " + id); + return true; +} + +function focusElementInChild(elementid, elementtype) { + let browser = elementid.includes("1") ? browser1 : browser2; + return SpecialPowers.spawn(browser, [elementid, elementtype], (id, type) => { + content.document.getElementById(id)[type](); + }); +} + +add_task(async function () { + tab1 = BrowserTestUtils.addTab(gBrowser); + browser1 = gBrowser.getBrowserForTab(tab1); + + tab2 = BrowserTestUtils.addTab(gBrowser); + browser2 = gBrowser.getBrowserForTab(tab2); + + await promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage1)); + await promiseTabLoadEvent(tab2, "data:text/html," + escape(testPage2)); + + gURLBar.focus(); + await SimpleTest.promiseFocus(); + + // In these listeners, focusInChild is used to cache details about the event + // on a temporary on the window (window._eventsOccurred), so that it can be + // retrieved later within compareFocusResults. focusInChild always returns true. + // compareFocusResults is called each time event occurs to check that the + // right events happened. + let listenersToRemove = []; + listenersToRemove.push( + BrowserTestUtils.addContentEventListener( + browser1, + "focus", + compareFocusResults, + { capture: true }, + focusInChild + ) + ); + listenersToRemove.push( + BrowserTestUtils.addContentEventListener( + browser1, + "blur", + compareFocusResults, + { capture: true }, + focusInChild + ) + ); + listenersToRemove.push( + BrowserTestUtils.addContentEventListener( + browser2, + "focus", + compareFocusResults, + { capture: true }, + focusInChild + ) + ); + listenersToRemove.push( + BrowserTestUtils.addContentEventListener( + browser2, + "blur", + compareFocusResults, + { capture: true }, + focusInChild + ) + ); + + // Get the content processes to do something, so that we can better + // ensure that the listeners added above will have actually been added + // in the tabs. + await SpecialPowers.spawn(browser1, [], () => {}); + await SpecialPowers.spawn(browser2, [], () => {}); + + _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 = await getFocusedElementForBrowser(browser1); + is(focused, "Focus is <none>", "initial focus in tab 1"); + + focused = await getFocusedElementForBrowser(browser2); + is(focused, "Focus is <none>", "initial focus in tab 2"); + + is( + document.activeElement, + gURLBar.inputField, + "focus after loading two tabs" + ); + + await expectFocusShiftAfterTabSwitch( + tab2, + "window2", + null, + true, + "after tab change, focus in new tab" + ); + + focused = await 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 + await expectFocusShiftAfterTabSwitch( + tab1, + "window1", + null, + true, + "after tab change, focus in original tab" + ); + + focused = await 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 + await expectFocusShift( + () => focusElementInChild("button1", "focus"), + "window1", + "button1", + true, + "after button focused" + ); + + focused = await 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. + await expectFocusShift( + () => focusElementInChild("button2", "focus"), + "window1", + "button1", + false, + "after button focus in unfocused tab" + ); + + focused = await getFocusedElementForBrowser(browser1, false); + is( + focused, + "Focus is button1", + "focusedElement in first browser after button focus in unfocused tab" + ); + focused = await 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 + await 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. + await expectFocusShift( + () => focusElementInChild("button1", "blur"), + "window2", + "button2", + false, + "focusedWindow after blur in unfocused tab" + ); + + focused = await getFocusedElementForBrowser(browser1, true); + is( + focused, + "Focus is <none>", + "focusedElement in first browser after focus in unfocused tab" + ); + focused = await 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 + await expectFocusShift( + () => gBrowser.selectedTab.focus(), + "main-window", + "tab2", + true, + "focusing tab element" + ); + await expectFocusShiftAfterTabSwitch( + tab1, + "main-window", + "tab1", + true, + "tab change when selected tab element was focused" + ); + + let switchWaiter = new Promise((resolve, reject) => { + gBrowser.addEventListener( + "TabSwitchDone", + function () { + executeSoon(resolve); + }, + { once: true } + ); + }); + + await expectFocusShiftAfterTabSwitch( + tab2, + "main-window", + "tab2", + true, + "another tab change when selected tab element was focused" + ); + + // 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. + await switchWaiter; + + await 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 + await focusElementInChild("button1", "focus"); + + await expectFocusShift( + () => gURLBar.focus(), + "main-window", + "urlbar", + true, + "focusedWindow after url field focused" + ); + focused = await getFocusedElementForBrowser(browser1, true); + is( + focused, + "Focus is button1", + "focusedElement after url field focused, first browser" + ); + focused = await getFocusedElementForBrowser(browser2, true); + is( + focused, + "Focus is button2", + "focusedElement after url field focused, second browser" + ); + + await 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 + await expectFocusShiftAfterTabSwitch( + tab1, + "window1", + "button1", + true, + "after tab change, focus in url field, button focused in new tab" + ); + + focused = await getFocusedElementForBrowser(browser1, false); + is( + focused, + "Focus is button1", + "after switch tab, focus in unfocused tab, first browser" + ); + focused = await 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 + await expectFocusShift( + () => focusElementInChild("button1", "blur"), + "window1", + null, + true, + "after blur in focused tab" + ); + + focused = await 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 + await 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 + await expectFocusShiftAfterTabSwitch( + tab2, + "window2", + "button2", + true, + "after switch from unfocused to focused tab" + ); + focused = await 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 + await 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 + await expectFocusShiftAfterTabSwitch( + tab1, + "window1", + null, + true, + "focusedWindow after tab switch from no focus to no focus" + ); + + focused = await getFocusedElementForBrowser(browser1, false); + is( + focused, + "Focus is <none>", + "after tab switch from no focus to no focus, first browser" + ); + focused = await 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. + await expectFocusShift( + () => focusElementInChild("button1", "focus"), + "window1", + "button1", + true, + "focus button" + ); + + await promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage3)); + + // now go back again + gURLBar.focus(); + + await new Promise((resolve, reject) => { + BrowserTestUtils.waitForContentEvent( + window.gBrowser.selectedBrowser, + "pageshow", + true + ).then(() => resolve()); + document.getElementById("Browser:Back").doCommand(); + }); + + is( + window.document.activeElement, + gURLBar.inputField, + "urlbar still focused after navigating back" + ); + + for (let listener of listenersToRemove) { + listener(); + } + + 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; + + if (Window.isInstance(event.target)) { + id = getWindowDocId(event.originalTarget) + "-window"; + } else if (Document.isInstance(event.target)) { + 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; +} + +async function compareFocusResults() { + if (!currentPromiseResolver) { + return; + } + + // Get the events that occurred in each child browser and store them + // in 'actualEvents'. This is a global so if different calls to + // compareFocusResults occur together, whichever one happens to get + // called first after pulling all the events from the child will + // perform the matching. + let events = await SpecialPowers.spawn(browser1, [], () => { + let eventsOccurred = content._eventsOccurred; + content._eventsOccurred = []; + return eventsOccurred || []; + }); + actualEvents.window1.push(...events); + + events = await SpecialPowers.spawn(browser2, [], () => { + let eventsOccurred = content._eventsOccurred; + content._eventsOccurred = []; + return eventsOccurred || []; + }); + actualEvents.window2.push(...events); + + // Another call to compareFocusResults may have happened in the meantime. + // If currentPromiseResolver is null, then that call was successful so no + // need to check the events again. + 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] = []; + } + + let matchWindow = window; + is(_expectedWindow, "main-window", "main-window is always expected"); + if (_expectedWindow == "main-window") { + // The browser window's body doesn't have an id set usually - set one now + // so it can be used for id comparisons below. + matchWindow.document.body.id = "main-window-body"; + } + + 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 = getId(matchWindow.document.body); + } + is( + getId(matchWindow.document.activeElement), + expectedActive, + currentTestName + " activeElement" + ); + + currentPromiseResolver(); + currentPromiseResolver = null; +} + +async function expectFocusShiftAfterTabSwitch( + tab, + expectedWindow, + expectedElement, + focusChanged, + testid +) { + let tabSwitchPromise = null; + await expectFocusShift( + () => { + tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, tab); + }, + expectedWindow, + expectedElement, + focusChanged, + testid + ); + await tabSwitchPromise; +} + +async 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 (_expectedWindow == "window1") { + _expectedElement = "browser1"; + } else if (_expectedWindow == "window2") { + _expectedElement = "browser2"; + } + _expectedWindow = "main-window"; + + if ( + _lastfocuswindow != "main-window" && + _lastfocuswindow != expectedWindow + ) { + let browserid = _lastfocuswindow == "window1" ? "browser1" : "browser2"; + expectedEvents.push("blur: " + browserid); + } + + var newElementIsFocused = + expectedElement && !expectedElement.startsWith("html"); + if ( + newElementIsFocused && + _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 (_lastfocuswindow != "main-window") { + expectedEvents.push("blur: " + _lastfocuswindow + "-document"); + expectedEvents.push("blur: " + _lastfocuswindow + "-window"); + } + } + + if (expectedWindow && _lastfocuswindow != expectedWindow) { + if (expectedWindow != "main-window") { + let browserid = expectedWindow == "window1" ? "browser1" : "browser2"; + expectedEvents.push("focus: " + browserid); + } + + if (expectedWindow != "main-window") { + expectedEvents.push("focus: " + expectedWindow + "-document"); + expectedEvents.push("focus: " + expectedWindow + "-window"); + } + } + + if (newElementIsFocused) { + expectedEvents.push("focus: " + expectedElement); + } + + _lastfocus = expectedElement; + _lastfocuswindow = expectedWindow; + } + + // No events are expected, so return immediately. If events do occur, the following + // tests will fail. + if ( + expectedEvents["main-window"].length + + expectedEvents.window1.length + + expectedEvents.window2.length == + 0 + ) { + await callback(); + return undefined; + } + + return new Promise(resolve => { + currentPromiseResolver = resolve; + callback(); + }); +} 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 0000000000..765bf5c21d --- /dev/null +++ b/browser/base/content/test/general/browser_tabkeynavigation.js @@ -0,0 +1,223 @@ +/* + * This test checks that keyboard navigation for tabs isn't blocked by content + */ +add_task(async 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 = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1); + let browser1 = gBrowser.getBrowserForTab(tab1); + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2); + let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.ctrlTab.sortByRecentlyUsed", false]], + }); + + // Disable tab animations + gReduceMotionOverride = true; + + 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.tabbox._handleMetaAltArrows) { + gBrowser.selectedTab = tab1; + browser1.focus(); + + let ltr = window.getComputedStyle(gBrowser.tabbox).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.tabbox).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(); + } +}); 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 0000000000..0534250970 --- /dev/null +++ b/browser/base/content/test/general/browser_tabs_close_beforeunload.js @@ -0,0 +1,69 @@ +"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(async function () { + info("Opening first tab"); + let firstTab = await 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"); + await SpecialPowers.spawn(firstTab.linkedBrowser, [""], async function () { + content.document.getElementsByTagName("a")[0].click(); + }); + info("Waiting for the second tab to be opened"); + await tabOpened; + info("Waiting for the load in that tab to finish"); + await secondTabLoadedPromise; + + let closeBtn = secondTab.closeButton; + info("closing second tab (which will self-close in beforeunload)"); + closeBtn.click(); + ok( + secondTab.closing, + "Second tab should be marked as closing synchronously." + ); + 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"); + 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 0000000000..3d485b01c1 --- /dev/null +++ b/browser/base/content/test/general/browser_tabs_isActive.js @@ -0,0 +1,235 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +// Test for the docshell active state of local and remote browsers. + +const kTestPage = + "https://example.org/browser/browser/base/content/test/general/dummy_page.html"; + +function promiseNewTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener( + "TabSwitchDone", + function () { + executeSoon(resolve); + }, + { once: true } + ); + }); +} + +function getParentTabState(aTab) { + return aTab.linkedBrowser.docShellIsActive; +} + +function getChildTabState(aTab) { + return ContentTask.spawn( + aTab.linkedBrowser, + null, + () => content.browsingContext.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(async function () { + let url = kTestPage; + let originalTab = gBrowser.selectedTab; // test tab + let newTab = BrowserTestUtils.addTab(gBrowser, url, { skipAnimation: true }); + let parentSide, childSide; + + // new tab added but not selected checks + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + false, + "newly added " + url + " tab is not active" + ); + parentSide = getParentTabState(originalTab); + childSide = await 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; + await 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 = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + true, + "newly added " + url + " tab is active after selection" + ); + parentSide = getParentTabState(originalTab); + childSide = await 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; + await tabSwitchedPromise; + + // check active state of both tabs + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + false, + "newly added " + url + " tab is not active after switch back" + ); + parentSide = getParentTabState(originalTab); + childSide = await 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; + await tabSwitchedPromise; + + // check active state of both tabs + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + true, + "newly added " + url + " tab is not active after switch back" + ); + parentSide = getParentTabState(originalTab); + childSide = await getChildTabState(originalTab); + checkState( + parentSide, + childSide, + false, + "original tab is active again after switch back" + ); + + gBrowser.removeTab(newTab); +}); + +add_task(async function () { + let url = "about:about"; + let originalTab = gBrowser.selectedTab; // test tab + let newTab = BrowserTestUtils.addTab(gBrowser, url, { skipAnimation: true }); + let parentSide, childSide; + + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + false, + "newly added " + url + " tab is not active" + ); + parentSide = getParentTabState(originalTab); + childSide = await getChildTabState(originalTab); + checkState(parentSide, childSide, true, "original tab is active initially"); + + let tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = newTab; + await tabSwitchedPromise; + + if (Services.appinfo.browserTabsRemoteAutostart) { + ok( + !newTab.linkedBrowser.isRemoteBrowser, + "for testing we need a local tab" + ); + } + + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + true, + "newly added " + url + " tab is active after selection" + ); + parentSide = getParentTabState(originalTab); + childSide = await getChildTabState(originalTab); + checkState( + parentSide, + childSide, + false, + "original tab is not active while unselected" + ); + + tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = originalTab; + await tabSwitchedPromise; + + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + false, + "newly added " + url + " tab is not active after switch back" + ); + parentSide = getParentTabState(originalTab); + childSide = await getChildTabState(originalTab); + checkState( + parentSide, + childSide, + true, + "original tab is active again after switch back" + ); + + tabSwitchedPromise = promiseNewTabSwitched(); + gBrowser.selectedTab = newTab; + await tabSwitchedPromise; + + parentSide = getParentTabState(newTab); + childSide = await getChildTabState(newTab); + checkState( + parentSide, + childSide, + true, + "newly added " + url + " tab is not active after switch back" + ); + parentSide = getParentTabState(originalTab); + childSide = await 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 0000000000..4a32da12f1 --- /dev/null +++ b/browser/base/content/test/general/browser_tabs_owner.js @@ -0,0 +1,40 @@ +function test() { + BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.addTab(gBrowser); + + var owner; + + is(gBrowser.tabs.length, 4, "4 tabs are open"); + + owner = gBrowser.selectedTab = gBrowser.tabs[2]; + BrowserOpenTab(); + is(gBrowser.selectedTab, gBrowser.tabs[4], "newly opened tab is selected"); + gBrowser.removeCurrentTab(); + is(gBrowser.selectedTab, owner, "owner is selected"); + + owner = gBrowser.selectedTab; + BrowserOpenTab(); + gBrowser.selectedTab = gBrowser.tabs[1]; + gBrowser.selectedTab = gBrowser.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 relationship persists when tab is moved" + ); + + while (gBrowser.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 0000000000..d5144b47b0 --- /dev/null +++ b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js @@ -0,0 +1,144 @@ +/* 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"; + +requestLongerTimeout(2); + +function insertAndClickAnchor(browser) { + return SpecialPowers.spawn(browser, [], () => { + content.document.body.innerHTML = ` + <a href="http://example.com/" target="_blank" rel="opener" 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) { + BrowserTestUtils.loadURIString(browser, 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(async function test_new_tab() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.skip_html_fragment_assertion", true]], + }); + + let normalWindow = await BrowserTestUtils.openNewBrowserWindow({ + remote: true, + }); + let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ + remote: true, + private: true, + }); + + for (let testWindow of [normalWindow, privateWindow]) { + await promiseWaitForFocus(testWindow); + let testBrowser = testWindow.gBrowser.selectedBrowser; + info("Preparing non-remote browser"); + await prepareNonRemoteBrowser(testWindow, testBrowser); + info("Non-remote browser prepared"); + + let tabOpenEventPromise = waitForNewTabEvent(testWindow.gBrowser); + await insertAndClickAnchor(testBrowser); + + let newTab = (await tabOpenEventPromise).target; + await promiseTabLoadEvent(newTab); + + // insertAndClickAnchor causes an open to a web page 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(async function test_new_window() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.skip_html_fragment_assertion", true]], + }); + + let normalWindow = await BrowserTestUtils.openNewBrowserWindow( + { + remote: true, + }, + true + ); + let privateWindow = await 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]) { + await promiseWaitForFocus(testWindow); + let testBrowser = testWindow.gBrowser.selectedBrowser; + await prepareNonRemoteBrowser(testWindow, testBrowser); + + await insertAndClickAnchor(testBrowser); + + // Click on the link in the browser, and wait for the new window. + let [newWindow] = await TestUtils.topicObserved( + "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; + + await promiseTabLoadEvent(newTab); + + // insertAndClickAnchor causes an open to a web page 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_typeAheadFind.js b/browser/base/content/test/general/browser_typeAheadFind.js new file mode 100644 index 0000000000..d68de34333 --- /dev/null +++ b/browser/base/content/test/general/browser_typeAheadFind.js @@ -0,0 +1,31 @@ +/* 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(async function () { + let testWindow = await BrowserTestUtils.openNewBrowserWindow(); + // The TabContextMenu initializes its strings only on a focus or mouseover event. + // Calls focus event on the TabContextMenu early in the test. + testWindow.gBrowser.selectedTab.focus(); + + BrowserTestUtils.loadURIString( + testWindow.gBrowser, + "data:text/html,<h1>A Page</h1>" + ); + await BrowserTestUtils.browserLoaded(testWindow.gBrowser.selectedBrowser); + + await SimpleTest.promiseFocus(testWindow.gBrowser.selectedBrowser); + + ok(!testWindow.gFindBarInitialized, "find bar is not initialized"); + + let findBarOpenPromise = BrowserTestUtils.waitForEvent( + testWindow.gBrowser, + "findbaropen" + ); + EventUtils.synthesizeKey("/", {}, testWindow); + await findBarOpenPromise; + + ok(testWindow.gFindBarInitialized, "find bar is now initialized"); + + await 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 0000000000..be55f06fae --- /dev/null +++ b/browser/base/content/test/general/browser_unknownContentType_title.js @@ -0,0 +1,88 @@ +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 = + // eslint-disable-next-line @microsoft/sdl/no-insecure-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"); + }); +} + +add_setup(async function () { + let tmpDir = PathUtils.join( + PathUtils.tempDir, + "testsavedir" + Math.floor(Math.random() * 2 ** 32) + ); + // Create this dir if it doesn't exist (ignores existing dirs) + await IOUtils.makeDirectory(tmpDir); + registerCleanupFunction(async function () { + try { + await IOUtils.remove(tmpDir, { recursive: true }); + } catch (e) { + console.error(e); + } + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + }); + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", tmpDir); +}); + +add_task(async function unknownContentType_title_with_pref_enabled() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", true]], + }); + + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url)); + let browser = tab.linkedBrowser; + await promiseTabLoaded(gBrowser.selectedTab); + + is(gBrowser.contentTitle, "Test Page", "Should have the right title."); + + BrowserTestUtils.loadURIString(browser, unknown_url); + let win = await waitForNewWindow(); + is( + win.location.href, + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + "Should have seen the unknown content dialog." + ); + is(gBrowser.contentTitle, "Test Page", "Should still have the right title."); + + win.close(); + await promiseWaitForFocus(window); + gBrowser.removeCurrentTab(); +}); + +add_task(async function unknownContentType_title_with_pref_disabled() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", false]], + }); + + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url)); + let browser = tab.linkedBrowser; + await promiseTabLoaded(gBrowser.selectedTab); + + is(gBrowser.contentTitle, "Test Page", "Should have the right title."); + + BrowserTestUtils.loadURIString(browser, unknown_url); + // If the pref is disabled, then the downloads panel should open right away + // since there is no UCT window prompt to block it. + let waitForPanelShown = BrowserTestUtils.waitForCondition(() => { + return DownloadsPanel.isPanelShowing; + }).then(() => "panel-shown"); + + let panelShown = await waitForPanelShown; + is(panelShown, "panel-shown", "The downloads panel is shown"); + is(gBrowser.contentTitle, "Test Page", "Should still have the right title."); + + 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 0000000000..7e0b48392b --- /dev/null +++ b/browser/base/content/test/general/browser_unloaddialogs.js @@ -0,0 +1,40 @@ +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(async function () { + for (let url of testUrls) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + ok(true, "Loaded page " + url); + // Wait one turn of the event loop before closing, so everything settles. + await new Promise(resolve => setTimeout(resolve, 0)); + BrowserTestUtils.removeTab(tab); + ok(true, "Closed page " + url + " without timeout"); + } +}); 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 0000000000..6c62670e6f --- /dev/null +++ b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js @@ -0,0 +1,60 @@ +function wait_while_tab_is_busy() { + return new Promise(resolve => { + let progressListener = { + onStateChange(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 _elementsForViewSource change happens at that time. +var with_new_tab_opened = async function (options, taskFn) { + let busyPromise = wait_while_tab_is_busy(); + let tab = await BrowserTestUtils.openNewForegroundTab( + options.gBrowser, + options.url, + false + ); + await busyPromise; + await taskFn(tab.linkedBrowser); + gBrowser.removeTab(tab); +}; + +add_task(async function test_regular_page() { + function test_expect_view_source_enabled(browser) { + for (let element of [...XULBrowserWindow._elementsForViewSource]) { + ok(!element.hasAttribute("disabled"), "View Source should be enabled"); + } + } + + await with_new_tab_opened( + { + gBrowser, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: "http://example.com", + }, + test_expect_view_source_enabled + ); +}); + +add_task(async function test_view_source_page() { + function test_expect_view_source_disabled(browser) { + for (let element of [...XULBrowserWindow._elementsForViewSource]) { + ok(element.hasAttribute("disabled"), "View Source should be disabled"); + } + } + + await 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 0000000000..56099521e2 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleFindSelection.js @@ -0,0 +1,62 @@ +add_task(async 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 = BrowserTestUtils.addTab(gBrowser)); + + await promiseTabLoadEvent( + tab, + "data:text/html;charset=utf-8," + escape(childContent) + ); + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + + let remote = gBrowser.selectedBrowser.isRemoteBrowser; + + let findBarOpenPromise = BrowserTestUtils.waitForEvent( + gBrowser, + "findbaropen" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await findBarOpenPromise; + + ok(gFindBarInitialized, "find bar is now initialized"); + + // Finds the div in the green box. + let scrollPromise = remote + ? BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "scroll") + : BrowserTestUtils.waitForEvent(gBrowser, "scroll"); + EventUtils.sendString("div"); + await scrollPromise; + + // Wait for one paint to ensure we've processed the previous key events and scrolling. + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + return new Promise(resolve => { + content.requestAnimationFrame(() => { + content.setTimeout(resolve, 0); + }); + }); + }); + + // Finds the div in the red box. + scrollPromise = remote + ? BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "scroll") + : BrowserTestUtils.waitForEvent(gBrowser, "scroll"); + EventUtils.synthesizeKey("g", { accelKey: true }); + await scrollPromise; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async 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("KEY_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 0000000000..7bf7dc1387 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs.js @@ -0,0 +1,125 @@ +/* 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(async function () { + // There should be one tab when we start the test + let [origTab] = gBrowser.visibleTabs; + + // Add a tab that will get pinned + let pinned = BrowserTestUtils.addTab(gBrowser); + gBrowser.pinTab(pinned); + + let testTab = BrowserTestUtils.addTab(gBrowser); + + let firefoxViewTab = BrowserTestUtils.addTab(gBrowser, "about:firefoxview"); + gBrowser.hideTab(firefoxViewTab); + + let visible = gBrowser.visibleTabs; + is(visible.length, 3, "3 tabs should be visible"); + 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 next to 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, 4, "4 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"); + + // select a hidden tab thats selectable + gBrowser.selectedTab = firefoxViewTab; + gBrowser.tabContainer.advanceSelectedTab(1, true); + is(gBrowser.selectedTab, pinned, "next to first visible tab, the pinned tab"); + gBrowser.tabContainer.advanceSelectedTab(1, true); + is(gBrowser.selectedTab, testTab, "next to second visible tab, the test tab"); + + // again select a hidden tab thats selectable + gBrowser.selectedTab = firefoxViewTab; + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(gBrowser.selectedTab, testTab, "next to last visible tab, the test tab"); + gBrowser.tabContainer.advanceSelectedTab(-1, true); + is(gBrowser.selectedTab, pinned, "next to first visible tab, the pinned tab"); + + // Try showing all tabs except for the Firefox View tab + gBrowser.showOnlyTheseTabs(Array.from(gBrowser.tabs.slice(0, 3))); + 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, 3, "still have 3 open tabs"); + + // Close the selectable hidden tab + gBrowser.removeTab(firefoxViewTab); + + // 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 0000000000..2c0002fc44 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js @@ -0,0 +1,35 @@ +/* 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 = BrowserTestUtils.addTab(gBrowser, "about:blank"); + let tabTwo = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/"); + gBrowser.selectedTab = tabTwo; + + let browser = gBrowser.getBrowserForTab(tabTwo); + BrowserTestUtils.browserLoaded(browser).then(() => { + 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); + for (let tab of gBrowser.tabs) { + gBrowser.showTab(tab); + } + + finish(); + }); +} 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 0000000000..ecc7228d65 --- /dev/null +++ b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js @@ -0,0 +1,52 @@ +/* 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(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.ctrlTab.sortByRecentlyUsed", true]], + }); + + let [origTab] = gBrowser.visibleTabs; + let tabOne = BrowserTestUtils.addTab(gBrowser); + let tabTwo = BrowserTestUtils.addTab(gBrowser); + + // test the ctrlTab.tabList + pressCtrlTab(); + ok(ctrlTab.isOpen, "With 3 tab open, Ctrl+Tab opens the preview panel"); + is(ctrlTab.tabList.length, 3, "Ctrl+Tab panel displays all visible tabs"); + releaseCtrl(); + + gBrowser.showOnlyTheseTabs([origTab]); + pressCtrlTab(); + ok( + !ctrlTab.isOpen, + "With 1 tab open, Ctrl+Tab doesn't open the preview panel" + ); + releaseCtrl(); + + gBrowser.showOnlyTheseTabs([origTab, tabOne, tabTwo]); + pressCtrlTab(); + ok( + ctrlTab.isOpen, + "Ctrl+Tab opens the preview panel after re-showing hidden tabs" + ); + is( + ctrlTab.tabList.length, + 3, + "Ctrl+Tab panel displays all visible tabs after re-showing hidden ones" + ); + releaseCtrl(); + + // cleanup + gBrowser.removeTab(tabOne); + gBrowser.removeTab(tabTwo); +}); + +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_windowactivation.js b/browser/base/content/test/general/browser_windowactivation.js new file mode 100644 index 0000000000..f5d30d7ac9 --- /dev/null +++ b/browser/base/content/test/general/browser_windowactivation.js @@ -0,0 +1,112 @@ +/* + * This test checks that window activation state is set properly with multiple tabs. + */ + +const testPageChrome = + getRootDirectory(gTestPath) + "file_window_activation.html"; +const testPageHttp = testPageChrome.replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com" +); +const testPageWindow = + getRootDirectory(gTestPath) + "file_window_activation2.html"; + +add_task(async function reallyRunTests() { + let chromeTab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + testPageChrome + ); + let chromeBrowser1 = chromeTab1.linkedBrowser; + + // This can't use openNewForegroundTab because if we focus chromeTab2 now, we + // won't send a focus event during test 6, further down in this file. + let chromeTab2 = BrowserTestUtils.addTab(gBrowser, testPageChrome); + let chromeBrowser2 = chromeTab2.linkedBrowser; + await BrowserTestUtils.browserLoaded(chromeBrowser2); + + let httpTab = BrowserTestUtils.addTab(gBrowser, testPageHttp); + let httpBrowser = httpTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(httpBrowser); + + function failTest() { + ok(false, "Test received unexpected activate/deactivate event"); + } + + // chrome:// url tabs should not receive "activate" or "deactivate" events + // as they should be sent to the top-level window in the parent process. + for (let b of [chromeBrowser1, chromeBrowser2]) { + BrowserTestUtils.waitForContentEvent(b, "activate", true).then(failTest); + BrowserTestUtils.waitForContentEvent(b, "deactivate", true).then(failTest); + } + + gURLBar.focus(); + + gBrowser.selectedTab = chromeTab1; + + // The test performs four checks, using -moz-window-inactive on three child + // tabs (2 loading chrome:// urls and one loading an http:// url). + // 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. + + // Step 1 - check the initial state + let colorChromeBrowser1 = await getBackgroundColor(chromeBrowser1, true); + let colorChromeBrowser2 = await getBackgroundColor(chromeBrowser2, true); + let colorHttpBrowser = await getBackgroundColor(httpBrowser, true); + is(colorChromeBrowser1, "rgba(0, 0, 0, 0)", "first tab initial"); + is(colorChromeBrowser2, "rgba(0, 0, 0, 0)", "second tab initial"); + is(colorHttpBrowser, "rgba(0, 0, 0, 0)", "third tab initial"); + + // Step 2 - open and focus another window + let otherWindow = window.open(testPageWindow, "", "chrome"); + await SimpleTest.promiseFocus(otherWindow); + colorChromeBrowser1 = await getBackgroundColor(chromeBrowser1, false); + colorChromeBrowser2 = await getBackgroundColor(chromeBrowser2, false); + colorHttpBrowser = await getBackgroundColor(httpBrowser, false); + is(colorChromeBrowser1, "rgb(255, 0, 0)", "first tab lowered"); + is(colorChromeBrowser2, "rgb(255, 0, 0)", "second tab lowered"); + is(colorHttpBrowser, "rgb(255, 0, 0)", "third tab lowered"); + + // Step 3 - close the other window again + otherWindow.close(); + colorChromeBrowser1 = await getBackgroundColor(chromeBrowser1, true); + colorChromeBrowser2 = await getBackgroundColor(chromeBrowser2, true); + colorHttpBrowser = await getBackgroundColor(httpBrowser, true); + is(colorChromeBrowser1, "rgba(0, 0, 0, 0)", "first tab raised"); + is(colorChromeBrowser2, "rgba(0, 0, 0, 0)", "second tab raised"); + is(colorHttpBrowser, "rgba(0, 0, 0, 0)", "third tab raised"); + + // Step 4 - switch to the second tab + gBrowser.selectedTab = chromeTab2; + colorChromeBrowser1 = await getBackgroundColor(chromeBrowser1, true); + colorChromeBrowser2 = await getBackgroundColor(chromeBrowser2, true); + colorHttpBrowser = await getBackgroundColor(httpBrowser, true); + is(colorChromeBrowser1, "rgba(0, 0, 0, 0)", "first tab after tab switch"); + is(colorChromeBrowser2, "rgba(0, 0, 0, 0)", "second tab after tab switch"); + is(colorHttpBrowser, "rgba(0, 0, 0, 0)", "third tab after tab switch"); + + BrowserTestUtils.removeTab(chromeTab1); + BrowserTestUtils.removeTab(chromeTab2); + BrowserTestUtils.removeTab(httpTab); + otherWindow = null; +}); + +function getBackgroundColor(browser, expectedActive) { + return SpecialPowers.spawn( + browser, + [!expectedActive], + async hasPseudoClass => { + let area = content.document.getElementById("area"); + await ContentTaskUtils.waitForCondition(() => { + return area; + }, "Page has loaded"); + await ContentTaskUtils.waitForCondition(() => { + return area.matches(":-moz-window-inactive") == hasPseudoClass; + }, `Window is considered ${hasPseudoClass ? "inactive" : "active"}`); + + return content.getComputedStyle(area).backgroundColor; + } + ); +} 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 0000000000..4aa6bfbb9c --- /dev/null +++ b/browser/base/content/test/general/browser_zbug569342.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function findBarDisabledOnSomePages() { + ok(!gFindBar || gFindBar.hidden, "Find bar should not be visible by default"); + + let findbarOpenedPromise = BrowserTestUtils.waitForEvent( + gBrowser.selectedTab, + "TabFindInitialized" + ); + document.documentElement.focus(); + // Open the Find bar before we navigate to pages that shouldn't have it. + EventUtils.synthesizeKey("f", { accelKey: true }); + await findbarOpenedPromise; + ok(!gFindBar.hidden, "Find bar should be visible"); + + let urls = ["about:preferences", "about:addons"]; + + for (let url of urls) { + await testFindDisabled(url); + } + + // Make sure the find bar is re-enabled after disabled page is closed. + await testFindEnabled("about:about"); + gFindBar.close(); + ok(gFindBar.hidden, "Find bar should now be hidden"); +}); + +function testFindDisabled(url) { + return BrowserTestUtils.withNewTab(url, async function (browser) { + let waitForFindBar = async () => { + await new Promise(r => requestAnimationFrame(r)); + await new Promise(r => Services.tm.dispatchToMainThread(r)); + }; + ok( + !gFindBar || gFindBar.hidden, + "Find bar should not be visible at the start" + ); + await BrowserTestUtils.synthesizeKey("/", {}, browser); + await waitForFindBar(); + ok( + !gFindBar || gFindBar.hidden, + "Find bar should not be visible after fast find" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await waitForFindBar(); + ok( + !gFindBar || gFindBar.hidden, + "Find bar should not be visible after find command" + ); + ok( + document.getElementById("cmd_find").getAttribute("disabled"), + "Find command should be disabled" + ); + }); +} + +async function testFindEnabled(url) { + return BrowserTestUtils.withNewTab(url, async function (browser) { + ok( + !document.getElementById("cmd_find").getAttribute("disabled"), + "Find command should not be disabled" + ); + + // Open Find bar and then close it. + let findbarOpenedPromise = BrowserTestUtils.waitForEvent( + gBrowser.selectedTab, + "TabFindInitialized" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await findbarOpenedPromise; + ok(!gFindBar.hidden, "Find bar should be visible again"); + EventUtils.synthesizeKey("KEY_Escape"); + ok(gFindBar.hidden, "Find bar should now be hidden"); + }); +} 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 0000000000..bfc24d817f --- /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 0000000000..e7c040bf1f --- /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 0000000000..c1f2b282fb --- /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/clipboard_pastefile.html b/browser/base/content/test/general/clipboard_pastefile.html new file mode 100644 index 0000000000..cffafcdb49 --- /dev/null +++ b/browser/base/content/test/general/clipboard_pastefile.html @@ -0,0 +1,52 @@ +<html><body> +<script> +async function checkPaste(event) { + let result = null; + try { + result = await checkPasteHelper(event); + } catch (e) { + result = e.toString(); + } + + document.dispatchEvent(new CustomEvent('testresult', { + detail: { result } + })); +} + +function is(a, b, msg) { + if (!Object.is(a, b)) { + throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`); + } +} + +async function checkPasteHelper(event) { + let dt = event.clipboardData; + + is(dt.types.length, 2, "Correct number of types"); + + // TODO: Remove application/x-moz-file from content. + is(dt.types[0], "application/x-moz-file", "First type") + is(dt.types[1], "Files", "Last type must be Files"); + + is(dt.getData("text/plain"), "", "text/plain found with getData"); + is(dt.getData("application/x-moz-file"), "", "application/x-moz-file found with getData"); + + is(dt.files.length, 1, "Correct number of files"); + is(dt.files[0].name, "test-file.txt", "Correct file name"); + is(dt.files[0].type, "text/plain", "Correct file type"); + + is(dt.items.length, 1, "Correct number of items"); + is(dt.items[0].kind, "file", "Correct item kind"); + is(dt.items[0].type, "text/plain", "Correct item type"); + + let file = dt.files[0]; + is(await file.text(), "Hello World!", "Pasted file contains right text"); + + return file.name; +} +</script> + +<input id="input" onpaste="checkPaste(event)"> + + +</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 0000000000..4b62002cc4 --- /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 0000000000..b17df8ee27 --- /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/download_page.html b/browser/base/content/test/general/download_page.html new file mode 100644 index 0000000000..300bacdb72 --- /dev/null +++ b/browser/base/content/test/general/download_page.html @@ -0,0 +1,72 @@ +<!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="download_page_1.txt" + 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.ogg" id="link3">Download "just some video.ogg"</a></li> + <li><a href="download_page_2.txt" + download="with-target.txt" id="link4">Download "with-target.txt"</a></li> + <li><a href="javascript:(1+2)+''" + download="javascript.html" id="link5">Download "javascript.html"</a></li> + <li><a href="#" download="test.blob" id=link6>Download "test.blob"</a></li> + <li><a href="#" download="test.file" id=link7>Download "test.file"</a></li> + <li><a href="download_with_content_disposition_header.sjs?inline=download_page_3.txt" + download="not_used.txt" id="link8">Download "download_page_3.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?attachment=download_page_3.txt" + download="not_used.txt" id="link9">Download "download_page_3.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?inline=none" + download="download_page_4.txt" id="link10">Download "download_page_4.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?attachment=none" + download="download_page_4.txt" id="link11">Download "download_page_4.txt"</a></li> + <li><a href="http://example.com/" + download="example.com" id="link12" target="_blank">Download "example.com"</a></li> + <li><a href="video.ogg" + download="no file extension" id="link13">Download "force extension"</a></li> + <li><a href="dummy.ics" + download="dummy.not-ics" id="link14">Download "dummy.not-ics"</a></li> + <li><a href="redirect_download.sjs?inline=download_page_3.txt" + download="not_used.txt" id="link15">Download "download_page_3.txt"</a></li> + <li><a href="redirect_download.sjs?attachment=download_page_3.txt" + download="not_used.txt" id="link16">Download "download_page_3.txt"</a></li> + <li><a href="redirect_download.sjs?inline=none" + download="download_page_4.txt" id="link17">Download "download_page_4.txt"</a></li> + <li><a href="redirect_download.sjs?attachment=none" + download="download_page_4.txt" id="link18">Download "download_page_4.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?inline;attachment=none" + download="download_page_4.txt" id="link19">Download "download_page_4.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?invalid=none" + download="download_page_4.txt" id="link20">Download "download_page_4.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?inline;attachment=download_page_4.txt" + download="download_page_4.txt" id="link21">Download "download_page_4.txt"</a></li> + <li><a href="download_with_content_disposition_header.sjs?invalid=download_page_4.txt" + download="download_page_4.txt" id="link22">Download "download_page_4.txt"</a></li> + </ul> + <div id="unload-flag">Okay</div> + + <script> + let blobURL = window.URL.createObjectURL(new Blob(["just text"], {type: "application/x-blob"})); + document.getElementById("link6").href = blobURL; + + let fileURL = window.URL.createObjectURL(new File(["just text"], + "wrong-file-name", {type: "application/x-some-file"})); + document.getElementById("link7").href = fileURL; + + window.addEventListener("beforeunload", function(evt) { + document.getElementById("unload-flag").textContent = "Fail"; + }); + </script> + </body> +</html> diff --git a/browser/base/content/test/general/download_page_1.txt b/browser/base/content/test/general/download_page_1.txt new file mode 100644 index 0000000000..404b2da2ad --- /dev/null +++ b/browser/base/content/test/general/download_page_1.txt @@ -0,0 +1 @@ +Hey What are you looking for? diff --git a/browser/base/content/test/general/download_page_2.txt b/browser/base/content/test/general/download_page_2.txt new file mode 100644 index 0000000000..9daeafb986 --- /dev/null +++ b/browser/base/content/test/general/download_page_2.txt @@ -0,0 +1 @@ +test diff --git a/browser/base/content/test/general/download_with_content_disposition_header.sjs b/browser/base/content/test/general/download_with_content_disposition_header.sjs new file mode 100644 index 0000000000..26be6c44b7 --- /dev/null +++ b/browser/base/content/test/general/download_with_content_disposition_header.sjs @@ -0,0 +1,19 @@ +/* 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 = "download"; + response.setStatusLine(request.httpVersion, "200", "OK"); + + let [first, second] = request.queryString.split("="); + let headerStr = first; + if (second !== "none") { + headerStr += "; filename=" + second; + } + + response.setHeader("Content-Disposition", headerStr); + response.setHeader("Content-Type", "text/plain", false); + response.setHeader("Content-Length", page.length + "", false); + response.write(page); +} diff --git a/browser/base/content/test/general/dummy.ics b/browser/base/content/test/general/dummy.ics new file mode 100644 index 0000000000..6100d46fb7 --- /dev/null +++ b/browser/base/content/test/general/dummy.ics @@ -0,0 +1,13 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//hacksw/handcal//NONSGML v1.0//EN +BEGIN:VEVENT +UID:uid1@example.com +DTSTAMP:19970714T170000Z +ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com +DTSTART:19970714T170000Z +DTEND:19970715T035959Z +SUMMARY:Bastille Day Party +GEO:48.85299;2.36885 +END:VEVENT +END:VCALENDAR
\ No newline at end of file diff --git a/browser/base/content/test/general/dummy.ics^headers^ b/browser/base/content/test/general/dummy.ics^headers^ new file mode 100644 index 0000000000..93e1fca48d --- /dev/null +++ b/browser/base/content/test/general/dummy.ics^headers^ @@ -0,0 +1 @@ +Content-Type: text/calendar 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 0000000000..1a87e28408 --- /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/file_documentnavigation_frameset.html b/browser/base/content/test/general/file_documentnavigation_frameset.html new file mode 100644 index 0000000000..beb01addfc --- /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 0000000000..0bead5efc6 --- /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_fullscreen-window-open.html b/browser/base/content/test/general/file_fullscreen-window-open.html new file mode 100644 index 0000000000..44ac3196a0 --- /dev/null +++ b/browser/base/content/test/general/file_fullscreen-window-open.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test for window.open() when browser is in fullscreen</title> + </head> + <body> + <script> + window.addEventListener("load", function() { + document.getElementById("test").addEventListener("click", onClick, true); + }, {capture: true, once: 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_window_activation.html b/browser/base/content/test/general/file_window_activation.html new file mode 100644 index 0000000000..dda62986d1 --- /dev/null +++ b/browser/base/content/test/general/file_window_activation.html @@ -0,0 +1,4 @@ +<body> +<style>:-moz-window-inactive { background-color: red; }</style> +<div id='area'></div> +</body> diff --git a/browser/base/content/test/general/file_window_activation2.html b/browser/base/content/test/general/file_window_activation2.html new file mode 100644 index 0000000000..e1b7ecf12f --- /dev/null +++ b/browser/base/content/test/general/file_window_activation2.html @@ -0,0 +1 @@ +<body>Hi</body> diff --git a/browser/base/content/test/general/file_with_link_to_http.html b/browser/base/content/test/general/file_with_link_to_http.html new file mode 100644 index 0000000000..4c1a766a3a --- /dev/null +++ b/browser/base/content/test/general/file_with_link_to_http.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Test page for Bug 1338375</title> +</head> +<body> + <a id="linkToExample" href="http://example.org" target="_blank">example.org</a> +</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 0000000000..fc3d2be19f --- /dev/null +++ b/browser/base/content/test/general/head.js @@ -0,0 +1,347 @@ +ChromeUtils.defineModuleGetter( + this, + "AboutNewTab", + "resource:///modules/AboutNewTab.jsm" +); +ChromeUtils.defineESModuleGetters(this, { + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); +ChromeUtils.defineModuleGetter( + this, + "TabCrashHandler", + "resource:///modules/ContentCrashHandlers.jsm" +); + +/** + * Wait for a <notification> to be closed then call the specified callback. + */ +function waitForNotificationClose(notification, cb) { + 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(notification.control.stack, { childList: true }); +} + +function closeAllNotifications() { + if (!gNotificationBox.currentNotification) { + return Promise.resolve(); + } + + return new Promise(resolve => { + for (let notification of gNotificationBox.allNotifications) { + waitForNotificationClose(notification, function () { + if (gNotificationBox.allNotifications.length === 0) { + resolve(); + } + }); + notification.close(); + } + }); +} + +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"); +} + +function openToolbarCustomizationUI(aCallback, aBrowserWin) { + if (!aBrowserWin) { + aBrowserWin = window; + } + + aBrowserWin.gCustomizeMode.enter(); + + aBrowserWin.gNavToolbox.addEventListener( + "customizationready", + function () { + executeSoon(function () { + aCallback(aBrowserWin); + }); + }, + { once: true } + ); +} + +function closeToolbarCustomizationUI(aCallback, aBrowserWin) { + aBrowserWin.gNavToolbox.addEventListener( + "aftercustomization", + function () { + executeSoon(aCallback); + }, + { once: true } + ); + + 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) { + return new Promise(resolve => { + waitForCondition(aConditionFn, resolve, "Condition didn't pass."); + }); +} + +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 pushPrefs(...aPrefs) { + return SpecialPowers.pushPrefEnv({ set: aPrefs }); +} + +function popPrefs() { + return SpecialPowers.popPrefEnv(); +} + +function promiseWindowClosed(win) { + let promise = BrowserTestUtils.domWindowClosed(win); + win.close(); + return promise; +} + +function promiseOpenAndLoadWindow(aOptions, aWaitForDelayedStartup = false) { + return new Promise(resolve => { + 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"); + resolve(win); + }, "browser-delayed-startup-finished"); + } else { + win.addEventListener( + "load", + function () { + resolve(win); + }, + { once: true } + ); + } + }); +} + +async function whenNewTabLoaded(aWindow, aCallback) { + aWindow.BrowserOpenTab(); + + let expectedURL = AboutNewTab.newTabURL; + let browser = aWindow.gBrowser.selectedBrowser; + let loadPromise = BrowserTestUtils.browserLoaded(browser, false, expectedURL); + let alreadyLoaded = await SpecialPowers.spawn(browser, [expectedURL], url => { + let doc = content.document; + return doc && doc.readyState === "complete" && doc.location.href == url; + }); + if (!alreadyLoaded) { + await loadPromise; + } + aCallback(); +} + +function whenTabLoaded(aTab, aCallback) { + promiseTabLoadEvent(aTab).then(aCallback); +} + +function promiseTabLoaded(aTab) { + return new Promise(resolve => { + whenTabLoaded(aTab, resolve); + }); +} + +/** + * 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.loadURIString(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 BrowserTestUtils.waitForEvent(aTabBrowser.tabContainer, "TabOpen"); +} + +function is_hidden(element) { + var style = element.ownerGlobal.getComputedStyle(element); + if (style.display == "none") { + return true; + } + if (style.visibility != "visible") { + return true; + } + if (XULPopupElement.isInstance(element)) { + return ["hiding", "closed"].includes(element.state); + } + + // Hiding a parent element will hide all its children + if (element.parentNode != element.ownerDocument) { + return is_hidden(element.parentNode); + } + + return false; +} + +function is_element_visible(element, msg) { + isnot(element, null, "Element should not be null, when checking visibility"); + ok(BrowserTestUtils.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 promisePopupShown(popup) { + return BrowserTestUtils.waitForPopupEvent(popup, "shown"); +} + +function promisePopupHidden(popup) { + return BrowserTestUtils.waitForPopupEvent(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; +} + +/** + * Resolves when a bookmark with the given uri is added. + */ +function promiseOnBookmarkItemAdded(aExpectedURI) { + return new Promise((resolve, reject) => { + let listener = events => { + is(events.length, 1, "Should only receive one event."); + info("Added a bookmark to " + events[0].url); + PlacesUtils.observers.removeListener(["bookmark-added"], listener); + if (events[0].url == aExpectedURI.spec) { + resolve(); + } else { + reject(new Error("Added an unexpected bookmark")); + } + }; + info("Waiting for a bookmark to be added"); + PlacesUtils.observers.addListener(["bookmark-added"], listener); + }); +} + +async function loadBadCertPage(url) { + let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url); + await loaded; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.document.getElementById("exceptionDialogButton").click(); + }); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); +} 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 0000000000..769c636340 --- /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 0000000000..6b0918941f --- /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/print_postdata.sjs b/browser/base/content/test/general/print_postdata.sjs new file mode 100644 index 0000000000..0e3ef38419 --- /dev/null +++ b/browser/base/content/test/general/print_postdata.sjs @@ -0,0 +1,25 @@ +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/redirect_download.sjs b/browser/base/content/test/general/redirect_download.sjs new file mode 100644 index 0000000000..c2857b9338 --- /dev/null +++ b/browser/base/content/test/general/redirect_download.sjs @@ -0,0 +1,11 @@ +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + let queryStr = request.queryString; + + response.setStatusLine("1.1", 302, "Found"); + response.setHeader( + "Location", + `download_with_content_disposition_header.sjs?${queryStr}`, + false + ); +} 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 0000000000..7d66e0a429 --- /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) { + Cu.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"); +} 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 0000000000..0f91507c18 --- /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) { + Cu.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); +} 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 0000000000..d864990e4f --- /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 0000000000..1136048d36 --- /dev/null +++ b/browser/base/content/test/general/test_bug628179.html @@ -0,0 +1,9 @@ +<!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_remoteTroubleshoot.html b/browser/base/content/test/general/test_remoteTroubleshoot.html new file mode 100644 index 0000000000..c0c3f5e604 --- /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.includes("object")) { + 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 0000000000..80390a3cca --- /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" 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" href="#"> + <text x="10px" y="192px" font-size="24px"> + This text contains <title> + <title> + This is a title + </title> + </text> + </a> + <a id="link3" 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" 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/unknownContentType_file.pif b/browser/base/content/test/general/unknownContentType_file.pif new file mode 100644 index 0000000000..9353d13126 --- /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 0000000000..09b22facc0 --- /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 0000000000..ac7ece3519 --- /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 0000000000..467fb0ce1c --- /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 0000000000..093158432a --- /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 0000000000..4511e92552 --- /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 + |