summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/general
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/base/content/test/general
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/base/content/test/general')
-rw-r--r--browser/base/content/test/general/alltabslistener.html8
-rw-r--r--browser/base/content/test/general/app_bug575561.html18
-rw-r--r--browser/base/content/test/general/app_subframe_bug575561.html12
-rw-r--r--browser/base/content/test/general/audio.oggbin0 -> 14293 bytes
-rw-r--r--browser/base/content/test/general/browser.toml536
-rw-r--r--browser/base/content/test/general/browser_accesskeys.js202
-rw-r--r--browser/base/content/test/general/browser_addCertException.js77
-rw-r--r--browser/base/content/test/general/browser_alltabslistener.js332
-rw-r--r--browser/base/content/test/general/browser_backButtonFitts.js40
-rw-r--r--browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js114
-rw-r--r--browser/base/content/test/general/browser_bug1261299.js112
-rw-r--r--browser/base/content/test/general/browser_bug1297539.js126
-rw-r--r--browser/base/content/test/general/browser_bug1299667.js70
-rw-r--r--browser/base/content/test/general/browser_bug321000.js91
-rw-r--r--browser/base/content/test/general/browser_bug356571.js101
-rw-r--r--browser/base/content/test/general/browser_bug380960.js18
-rw-r--r--browser/base/content/test/general/browser_bug406216.js64
-rw-r--r--browser/base/content/test/general/browser_bug417483.js50
-rw-r--r--browser/base/content/test/general/browser_bug424101.js72
-rw-r--r--browser/base/content/test/general/browser_bug427559.js41
-rw-r--r--browser/base/content/test/general/browser_bug431826.js59
-rw-r--r--browser/base/content/test/general/browser_bug432599.js109
-rw-r--r--browser/base/content/test/general/browser_bug455852.js27
-rw-r--r--browser/base/content/test/general/browser_bug462289.js144
-rw-r--r--browser/base/content/test/general/browser_bug462673.js66
-rw-r--r--browser/base/content/test/general/browser_bug477014.js36
-rw-r--r--browser/base/content/test/general/browser_bug479408.js23
-rw-r--r--browser/base/content/test/general/browser_bug479408_sample.html4
-rw-r--r--browser/base/content/test/general/browser_bug481560.js16
-rw-r--r--browser/base/content/test/general/browser_bug484315.js14
-rw-r--r--browser/base/content/test/general/browser_bug491431.js42
-rw-r--r--browser/base/content/test/general/browser_bug495058.js53
-rw-r--r--browser/base/content/test/general/browser_bug519216.js48
-rw-r--r--browser/base/content/test/general/browser_bug520538.js27
-rw-r--r--browser/base/content/test/general/browser_bug521216.js68
-rw-r--r--browser/base/content/test/general/browser_bug533232.js56
-rw-r--r--browser/base/content/test/general/browser_bug537013.js168
-rw-r--r--browser/base/content/test/general/browser_bug537474.js20
-rw-r--r--browser/base/content/test/general/browser_bug563588.js42
-rw-r--r--browser/base/content/test/general/browser_bug565575.js21
-rw-r--r--browser/base/content/test/general/browser_bug567306.js65
-rw-r--r--browser/base/content/test/general/browser_bug575561.js118
-rw-r--r--browser/base/content/test/general/browser_bug577121.js27
-rw-r--r--browser/base/content/test/general/browser_bug578534.js31
-rw-r--r--browser/base/content/test/general/browser_bug579872.js26
-rw-r--r--browser/base/content/test/general/browser_bug581253.js74
-rw-r--r--browser/base/content/test/general/browser_bug585785.js48
-rw-r--r--browser/base/content/test/general/browser_bug585830.js27
-rw-r--r--browser/base/content/test/general/browser_bug594131.js25
-rw-r--r--browser/base/content/test/general/browser_bug596687.js28
-rw-r--r--browser/base/content/test/general/browser_bug597218.js40
-rw-r--r--browser/base/content/test/general/browser_bug609700.js28
-rw-r--r--browser/base/content/test/general/browser_bug623893.js50
-rw-r--r--browser/base/content/test/general/browser_bug624734.js49
-rw-r--r--browser/base/content/test/general/browser_bug664672.js27
-rw-r--r--browser/base/content/test/general/browser_bug676619.js225
-rw-r--r--browser/base/content/test/general/browser_bug710878.js49
-rw-r--r--browser/base/content/test/general/browser_bug724239.js56
-rw-r--r--browser/base/content/test/general/browser_bug734076.js195
-rw-r--r--browser/base/content/test/general/browser_bug749738.js32
-rw-r--r--browser/base/content/test/general/browser_bug763468_perwindowpb.js57
-rw-r--r--browser/base/content/test/general/browser_bug767836_perwindowpb.js72
-rw-r--r--browser/base/content/test/general/browser_bug817947.js51
-rw-r--r--browser/base/content/test/general/browser_bug832435.js25
-rw-r--r--browser/base/content/test/general/browser_bug882977.js33
-rw-r--r--browser/base/content/test/general/browser_bug963945.js26
-rw-r--r--browser/base/content/test/general/browser_clipboard.js290
-rw-r--r--browser/base/content/test/general/browser_clipboard_pastefile.js133
-rw-r--r--browser/base/content/test/general/browser_contentAltClick.js205
-rw-r--r--browser/base/content/test/general/browser_contentAreaClick.js329
-rw-r--r--browser/base/content/test/general/browser_ctrlTab.js464
-rw-r--r--browser/base/content/test/general/browser_datachoices_notification.js293
-rw-r--r--browser/base/content/test/general/browser_documentnavigation.js493
-rw-r--r--browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js237
-rw-r--r--browser/base/content/test/general/browser_double_close_tab.js120
-rw-r--r--browser/base/content/test/general/browser_drag.js58
-rw-r--r--browser/base/content/test/general/browser_duplicateIDs.js11
-rw-r--r--browser/base/content/test/general/browser_findbarClose.js47
-rw-r--r--browser/base/content/test/general/browser_focusonkeydown.js34
-rw-r--r--browser/base/content/test/general/browser_fullscreen-window-open.js366
-rw-r--r--browser/base/content/test/general/browser_gestureSupport.js1150
-rw-r--r--browser/base/content/test/general/browser_hide_removing.js27
-rw-r--r--browser/base/content/test/general/browser_homeDrop.js111
-rw-r--r--browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js48
-rw-r--r--browser/base/content/test/general/browser_lastAccessedTab.js71
-rw-r--r--browser/base/content/test/general/browser_menuButtonFitts.js69
-rw-r--r--browser/base/content/test/general/browser_middleMouse_noJSPaste.js49
-rw-r--r--browser/base/content/test/general/browser_minimize.js49
-rw-r--r--browser/base/content/test/general/browser_modifiedclick_inherit_principal.js42
-rw-r--r--browser/base/content/test/general/browser_newTabDrop.js218
-rw-r--r--browser/base/content/test/general/browser_newWindowDrop.js225
-rw-r--r--browser/base/content/test/general/browser_new_http_window_opened_from_file_tab.js63
-rw-r--r--browser/base/content/test/general/browser_newwindow_focus.js93
-rw-r--r--browser/base/content/test/general/browser_plainTextLinks.js237
-rw-r--r--browser/base/content/test/general/browser_printpreview.js43
-rw-r--r--browser/base/content/test/general/browser_private_browsing_window.js133
-rw-r--r--browser/base/content/test/general/browser_private_no_prompt.js12
-rw-r--r--browser/base/content/test/general/browser_refreshBlocker.js211
-rw-r--r--browser/base/content/test/general/browser_relatedTabs.js74
-rw-r--r--browser/base/content/test/general/browser_remoteTroubleshoot.js130
-rw-r--r--browser/base/content/test/general/browser_remoteWebNavigation_postdata.js55
-rw-r--r--browser/base/content/test/general/browser_restore_isAppTab.js87
-rw-r--r--browser/base/content/test/general/browser_save_link-perwindowpb.js214
-rw-r--r--browser/base/content/test/general/browser_save_link_when_window_navigates.js197
-rw-r--r--browser/base/content/test/general/browser_save_private_link_perwindowpb.js127
-rw-r--r--browser/base/content/test/general/browser_save_video.js99
-rw-r--r--browser/base/content/test/general/browser_save_video_frame.js103
-rw-r--r--browser/base/content/test/general/browser_selectTabAtIndex.js89
-rw-r--r--browser/base/content/test/general/browser_star_hsts.js87
-rw-r--r--browser/base/content/test/general/browser_star_hsts.sjs12
-rw-r--r--browser/base/content/test/general/browser_storagePressure_notification.js182
-rw-r--r--browser/base/content/test/general/browser_tabDrop.js204
-rw-r--r--browser/base/content/test/general/browser_tab_close_dependent_window.js35
-rw-r--r--browser/base/content/test/general/browser_tab_detach_restore.js54
-rw-r--r--browser/base/content/test/general/browser_tab_drag_drop_perwindow.js417
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop.js257
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop2.js65
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop2_frame1.xhtml158
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop_embed.html2
-rw-r--r--browser/base/content/test/general/browser_tabfocus.js811
-rw-r--r--browser/base/content/test/general/browser_tabkeynavigation.js223
-rw-r--r--browser/base/content/test/general/browser_tabs_close_beforeunload.js69
-rw-r--r--browser/base/content/test/general/browser_tabs_isActive.js235
-rw-r--r--browser/base/content/test/general/browser_tabs_owner.js40
-rw-r--r--browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js144
-rw-r--r--browser/base/content/test/general/browser_typeAheadFind.js31
-rw-r--r--browser/base/content/test/general/browser_unknownContentType_title.js88
-rw-r--r--browser/base/content/test/general/browser_unloaddialogs.js40
-rw-r--r--browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js60
-rw-r--r--browser/base/content/test/general/browser_visibleFindSelection.js62
-rw-r--r--browser/base/content/test/general/browser_visibleTabs.js125
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js35
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_tabPreview.js52
-rw-r--r--browser/base/content/test/general/browser_windowactivation.js112
-rw-r--r--browser/base/content/test/general/browser_zbug569342.js77
-rw-r--r--browser/base/content/test/general/bug792517-2.html5
-rw-r--r--browser/base/content/test/general/bug792517.html5
-rw-r--r--browser/base/content/test/general/bug792517.sjs13
-rw-r--r--browser/base/content/test/general/clipboard_pastefile.html52
-rw-r--r--browser/base/content/test/general/close_beforeunload.html8
-rw-r--r--browser/base/content/test/general/close_beforeunload_opens_second_tab.html3
-rw-r--r--browser/base/content/test/general/download_page.html72
-rw-r--r--browser/base/content/test/general/download_page_1.txt1
-rw-r--r--browser/base/content/test/general/download_page_2.txt1
-rw-r--r--browser/base/content/test/general/download_with_content_disposition_header.sjs19
-rw-r--r--browser/base/content/test/general/dummy.ics13
-rw-r--r--browser/base/content/test/general/dummy.ics^headers^1
-rw-r--r--browser/base/content/test/general/dummy_page.html9
-rw-r--r--browser/base/content/test/general/file_documentnavigation_frameset.html12
-rw-r--r--browser/base/content/test/general/file_double_close_tab.html15
-rw-r--r--browser/base/content/test/general/file_fullscreen-window-open.html22
-rw-r--r--browser/base/content/test/general/file_window_activation.html4
-rw-r--r--browser/base/content/test/general/file_window_activation2.html1
-rw-r--r--browser/base/content/test/general/file_with_link_to_http.html9
-rw-r--r--browser/base/content/test/general/head.js339
-rw-r--r--browser/base/content/test/general/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/general/navigating_window_with_download.html7
-rw-r--r--browser/base/content/test/general/print_postdata.sjs25
-rw-r--r--browser/base/content/test/general/redirect_download.sjs11
-rw-r--r--browser/base/content/test/general/refresh_header.sjs23
-rw-r--r--browser/base/content/test/general/refresh_meta.sjs35
-rw-r--r--browser/base/content/test/general/test_bug462673.html18
-rw-r--r--browser/base/content/test/general/test_bug628179.html9
-rw-r--r--browser/base/content/test/general/test_remoteTroubleshoot.html50
-rw-r--r--browser/base/content/test/general/title_test.svg59
-rw-r--r--browser/base/content/test/general/unknownContentType_file.pif1
-rw-r--r--browser/base/content/test/general/unknownContentType_file.pif^headers^1
-rw-r--r--browser/base/content/test/general/video.oggbin0 -> 285310 bytes
-rw-r--r--browser/base/content/test/general/web_video.html10
-rw-r--r--browser/base/content/test/general/web_video1.ogvbin0 -> 28942 bytes
-rw-r--r--browser/base/content/test/general/web_video1.ogv^headers^3
171 files changed, 16250 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
new file mode 100644
index 0000000000..477544875d
--- /dev/null
+++ b/browser/base/content/test/general/audio.ogg
Binary files differ
diff --git a/browser/base/content/test/general/browser.toml b/browser/base/content/test/general/browser.toml
new file mode 100644
index 0000000000..6928ba2d4b
--- /dev/null
+++ b/browser/base/content/test/general/browser.toml
@@ -0,0 +1,536 @@
+###############################################################################
+# 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_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_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_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
+# 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"]
+skip-if = ["a11y_checks"] # Bugs 1858041 and 1835079 for causing intermittent crashes
+# 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",
+ "win11_2009 && 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"]
+# 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 = ["win11_2009 && 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..0809553404
--- /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("aria-label", "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..c7829d16fe
--- /dev/null
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -0,0 +1,332 @@
+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) {
+ Assert.equal(
+ 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.startLoadingURIString(gBackgroundBrowser, kBasePage);
+ BrowserTestUtils.startLoadingURIString(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.startLoadingURIString(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..dbd8b272fd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1297539.js
@@ -0,0 +1,126 @@
+/* -*- 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,
+ SpecialPowers.wrap(window).browsingContext.currentWindowContext
+ );
+ 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..aa3569c93d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug356571.js
@@ -0,0 +1,101 @@
+// 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
+ Assert.equal(
+ 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..f8189fb268
--- /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:robots",
+ "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..68e2e99511
--- /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.startLoadingURIString(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..f90de6c530
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug431826.js
@@ -0,0 +1,59 @@
+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.startLoadingURIString(
+ 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..c1c848aae3
--- /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.startLoadingURIString(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..ccb467a234
--- /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.startLoadingURIString(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..b9446e3d34
--- /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.startLoadingURIString(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..79cd10c591
--- /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.startLoadingURIString(
+ browser,
+ "data:text/plain;charset=utf-8,2"
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.startLoadingURIString(
+ 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..df2dcb4b0d
--- /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.startLoadingURIString(
+ 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..8aeb5f6221
--- /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.startLoadingURIString(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.startLoadingURIString(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.startLoadingURIString(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..bd86f8e2b3
--- /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.startLoadingURIString(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..ea3c39222e
--- /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 = Promise.withResolvers();
+
+ 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..9a6b781f91
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug832435.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/. */
+
+ChromeUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
+ const { UrlbarTestUtils: module } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+ );
+ module.init(this);
+ return module;
+});
+
+add_task(async function test() {
+ gBrowser.selectedBrowser.focus();
+ await UrlbarTestUtils.inputIntoURLBar(
+ window,
+ "javascript: var foo = '11111111'; "
+ );
+ ok(gURLBar.focused, "Address bar is focused");
+ EventUtils.synthesizeKey("VK_RETURN");
+
+ // javscript: URIs are evaluated async.
+ await new Promise(resolve => SimpleTest.executeSoon(resolve));
+ ok(true, "Evaluated without crashing");
+});
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..490b2aea10
--- /dev/null
+++ b/browser/base/content/test/general/browser_datachoices_notification.js
@@ -0,0 +1,293 @@
+/* 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 = Promise.withResolvers();
+ 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 = Promise.withResolvers();
+ 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];
+
+ let openPrefsPromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+
+ // Click on the button.
+ button.click();
+
+ // Wait for the preferences panel to open.
+ await openPrefsPromise;
+};
+
+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;
+ await promiseNextTick();
+
+ 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..880db6110f
--- /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.startLoadingURIString(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..a8dcf9d995
--- /dev/null
+++ b/browser/base/content/test/general/browser_drag.js
@@ -0,0 +1,58 @@
+async function test() {
+ waitForExplicitFinish();
+
+ // ---- 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
+ );
+ Assert.strictEqual(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..996f01e0b9
--- /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.startLoadingURIString(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..90978a547e
--- /dev/null
+++ b/browser/base/content/test/general/browser_gestureSupport.js
@@ -0,0 +1,1150 @@
+/* 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) =>
+ Assert.equal(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
+ );
+ Assert.equal(cmdInc.callCount, 0, "Increasing command was triggered");
+ Assert.equal(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
+ );
+ Assert.equal(cmdInc.callCount, 1, "Increasing command was not triggered");
+ Assert.equal(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
+ );
+ Assert.equal(cmdInc.callCount, 0, "Increasing command was triggered");
+ Assert.equal(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
+ );
+ Assert.equal(cmdInc.callCount, 0, "Increasing command was triggered");
+ Assert.equal(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
+ );
+ Assert.equal(cmdInc.callCount, 0, "Increasing command was triggered");
+ Assert.equal(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
+ );
+ Assert.equal(cmdUp.callCount, 1, "Step 1: Up command was not triggered");
+ Assert.equal(cmdDown.callCount, 0, "Step 1: Down command was triggered");
+ Assert.equal(cmdLeft.callCount, 0, "Step 1: Left command was triggered");
+ Assert.equal(cmdRight.callCount, 0, "Step 1: Right command was triggered");
+
+ // DOWN
+ resetCounts();
+ await synthesizeSimpleGestureEvent(
+ test_normalTab.linkedBrowser,
+ "MozSwipeGesture",
+ 10,
+ 10,
+ down,
+ 0,
+ 0,
+ 0
+ );
+ Assert.equal(cmdUp.callCount, 0, "Step 2: Up command was triggered");
+ Assert.equal(cmdDown.callCount, 1, "Step 2: Down command was not triggered");
+ Assert.equal(cmdLeft.callCount, 0, "Step 2: Left command was triggered");
+ Assert.equal(cmdRight.callCount, 0, "Step 2: Right command was triggered");
+
+ // LEFT
+ resetCounts();
+ await synthesizeSimpleGestureEvent(
+ test_normalTab.linkedBrowser,
+ "MozSwipeGesture",
+ 10,
+ 10,
+ left,
+ 0,
+ 0,
+ 0
+ );
+ Assert.equal(cmdUp.callCount, 0, "Step 3: Up command was triggered");
+ Assert.equal(cmdDown.callCount, 0, "Step 3: Down command was triggered");
+ Assert.equal(cmdLeft.callCount, 1, "Step 3: Left command was not triggered");
+ Assert.equal(cmdRight.callCount, 0, "Step 3: Right command was triggered");
+
+ // RIGHT
+ resetCounts();
+ await synthesizeSimpleGestureEvent(
+ test_normalTab.linkedBrowser,
+ "MozSwipeGesture",
+ 10,
+ 10,
+ right,
+ 0,
+ 0,
+ 0
+ );
+ Assert.equal(cmdUp.callCount, 0, "Step 4: Up command was triggered");
+ Assert.equal(cmdDown.callCount, 0, "Step 4: Down command was triggered");
+ Assert.equal(cmdLeft.callCount, 0, "Step 4: Left command was triggered");
+ Assert.equal(
+ 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
+ );
+ Assert.equal(
+ cmdUp.callCount,
+ 0,
+ "Step 5-" + i + ": Up command was triggered"
+ );
+ Assert.equal(
+ cmdDown.callCount,
+ 0,
+ "Step 5-" + i + ": Down command was triggered"
+ );
+ Assert.equal(
+ cmdLeft.callCount,
+ 0,
+ "Step 5-" + i + ": Left command was triggered"
+ );
+ Assert.equal(
+ 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..4362f3456e
--- /dev/null
+++ b/browser/base/content/test/general/browser_homeDrop.js
@@ -0,0 +1,111 @@
+/* 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"]);
+
+ // 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..95b7898369
--- /dev/null
+++ b/browser/base/content/test/general/browser_lastAccessedTab.js
@@ -0,0 +1,71 @@
+/* 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);
+ Assert.lessOrEqual(
+ 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() {
+ Assert.less(
+ 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() {
+ Assert.less(
+ 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..ea4bb569a4
--- /dev/null
+++ b/browser/base/content/test/general/browser_newTabDrop.js
@@ -0,0 +1,218 @@
+/* eslint-disable @microsoft/sdl/no-insecure-url */
+
+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("example.com/first", ["http://example.com/first"]);
+});
+add_task(async function single_url2() {
+ await dropText("example.com/second", ["http://example.com/second"]);
+});
+add_task(async function single_url3() {
+ await dropText("example.com/third", ["http://example.com/third"]);
+});
+
+// Single text/plain item, with multiple links.
+add_task(async function multiple_urls() {
+ await dropText("www.example.com/1\nexample.com/2", [
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.example.com/1",
+ "http://example.com/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: "example.com/5" }],
+ [{ type: "text/plain", data: "example.com/6\nexample.com/7" }],
+ ],
+ ["http://example.com/5", "http://example.com/6", "http://example.com/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: "example.com/8\nTITLE8\nexample.com/9\nTITLE9",
+ },
+ ],
+ ],
+ ["http://example.com/8", "http://example.com/9"]
+ );
+});
+
+// Single item with multiple types.
+add_task(async function single_item_multiple_types() {
+ await drop(
+ [
+ [
+ { type: "text/plain", data: "example.com/10" },
+ { type: "text/x-moz-url", data: "example.com/11\nTITLE11" },
+ ],
+ ],
+ ["http://example.com/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("example.com/multi" + i);
+ }
+ await dropText(urls.join("\n"), [
+ "http://example.com/multi0",
+ "http://example.com/multi1",
+ "http://example.com/multi2",
+ "http://example.com/multi3",
+ "http://example.com/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("example.com/accept" + i);
+ }
+ await dropText(urls.join("\n"), [
+ "http://example.com/accept0",
+ "http://example.com/accept1",
+ "http://example.com/accept2",
+ "http://example.com/accept3",
+ "http://example.com/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("example.com/cancel" + i);
+ }
+ await dropText(urls.join("\n"), []);
+
+ await confirmPromise;
+
+ await popPrefs();
+});
+
+// Open URLs ignoring non-URL.
+add_task(async function multiple_urls() {
+ await dropText(
+ `
+ example.com/urls0
+ example.com/urls1
+ example.com/urls2
+ non url0
+ example.com/urls3
+ non url1
+ non url2
+`,
+ [
+ "http://example.com/urls0",
+ "http://example.com/urls1",
+ "http://example.com/urls2",
+ "http://example.com/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}`
+ );
+
+ // 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, true, 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..3e41b0d6ac
--- /dev/null
+++ b/browser/base/content/test/general/browser_newWindowDrop.js
@@ -0,0 +1,225 @@
+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}`
+ );
+
+ // 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..332d7fa029
--- /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.isVisible(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..637f47a545
--- /dev/null
+++ b/browser/base/content/test/general/browser_refreshBlocker.js
@@ -0,0 +1,211 @@
+"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.startLoadingURIString(
+ 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.startLoadingURIString(
+ 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 [redirectLabel, refreshLabel] = await document.l10n.formatValues([
+ { id: "refresh-blocked-redirect-label" },
+ { id: "refresh-blocked-refresh-label" },
+ ]);
+
+ is(
+ notification.messageText.textContent.trim(),
+ redirectLabel,
+ "Should be showing the redirect message"
+ );
+
+ // Next, attempt a refresh
+ await attemptFakeRefresh(browser, false);
+
+ is(
+ notification.messageText.textContent.trim(),
+ refreshLabel,
+ "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..17b0eb7cbe
--- /dev/null
+++ b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js
@@ -0,0 +1,55 @@
+/* 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.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+ );
+ 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..d46f53db7d
--- /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.startLoadingURIString(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.startLoadingURIString(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..b018212280
--- /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.startLoadingURIString(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..e7507fcbb0
--- /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.startLoadingURIString(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..276088fbb1
--- /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.startLoadingURIString(
+ 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..92c1dcdb8b
--- /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.startLoadingURIString(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..88c4a175b1
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabDrop.js
@@ -0,0 +1,204 @@
+/* eslint-disable @microsoft/sdl/no-insecure-url */
+
+// 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("example.com/first", ["http://example.com/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("example.com/second", ["http://example.com/second"]);
+});
+add_task(async function single_data_url() {
+ await dropText("data:text/html,bad", []);
+});
+add_task(async function single_url3() {
+ await dropText("example.com/third", ["http://example.com/third"]);
+});
+
+// Single text/plain item, with multiple links.
+add_task(async function multiple_urls() {
+ await dropText("example.com/1\nexample.com/2", [
+ "http://example.com/1",
+ "http://example.com/2",
+ ]);
+});
+add_task(async function multiple_urls_javascript() {
+ await dropText("javascript:'bad1'\nexample.com/3", []);
+});
+add_task(async function multiple_urls_data() {
+ await dropText("example.com/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: "example.com/5" }],
+ [{ type: "text/plain", data: "example.com/6\nexample.com/7" }],
+ ],
+ ["http://example.com/5", "http://example.com/6", "http://example.com/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: "example.com/8\nTITLE8\nexample.com/9\nTITLE9",
+ },
+ ],
+ ],
+ ["http://example.com/8", "http://example.com/9"]
+ );
+});
+
+// Single item with multiple types.
+add_task(async function single_item_multiple_types() {
+ await drop(
+ [
+ [
+ { type: "text/plain", data: "example.com/10" },
+ { type: "text/x-moz-url", data: "example.com/11\nTITLE11" },
+ ],
+ ],
+ ["http://example.com/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("example.com/multi" + i);
+ }
+ await dropText(urls.join("\n"), [
+ "http://example.com/multi0",
+ "http://example.com/multi1",
+ "http://example.com/multi2",
+ "http://example.com/multi3",
+ "http://example.com/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("example.com/accept" + i);
+ }
+ await dropText(urls.join("\n"), [
+ "http://example.com/accept0",
+ "http://example.com/accept1",
+ "http://example.com/accept2",
+ "http://example.com/accept3",
+ "http://example.com/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("example.com/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 awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
+
+ let loadedPromises = expectedURLs.map(url =>
+ BrowserTestUtils.waitForNewTab(gBrowser, url, true, 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..405e4509b4
--- /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.startLoadingURIString(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..01519a6142
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
@@ -0,0 +1,417 @@
+/* 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);
+
+/**
+ * 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..33b0a5c238
--- /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.startLoadingURIString(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..49f8629a25
--- /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.startLoadingURIString(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..eee3775249
--- /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.startLoadingURIString(
+ 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..ea0b99ebf1
--- /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.startLoadingURIString(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.startLoadingURIString(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,&lt;html id='htmlframe1' &gt;&lt;body id='framebody1'&gt;&lt;input id='i1'&gt;&lt;body&gt;&lt;/html&gt;">
+ <frameset cols="30%, 33%, 34%">
+ <frame src="data:text/html,&lt;html id='htmlframe2'&gt;&lt;body id='framebody2'&gt;&lt;input id='i2'&gt;&lt;body&gt;&lt;/html&gt;">
+ <frame src="data:text/html,&lt;html id='htmlframe3'&gt;&lt;body id='framebody3'&gt;&lt;input id='i3'&gt;&lt;body&gt;&lt;/html&gt;">
+ <frame src="data:text/html,&lt;html id='htmlframe4'&gt;&lt;body id='framebody4'&gt;&lt;input id='i4'&gt;&lt;body&gt;&lt;/html&gt;">
+ </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..f7b4a0d93b
--- /dev/null
+++ b/browser/base/content/test/general/head.js
@@ -0,0 +1,339 @@
+ChromeUtils.defineESModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
+});
+
+/**
+ * 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.startLoadingURIString(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.isVisible(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.startLoadingURIString(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
new file mode 100644
index 0000000000..769c636340
--- /dev/null
+++ b/browser/base/content/test/general/moz.png
Binary files differ
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..6fb26605fc
--- /dev/null
+++ b/browser/base/content/test/general/refresh_header.sjs
@@ -0,0 +1,23 @@
+/**
+ * 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) {
+ 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..02beb17ac2
--- /dev/null
+++ b/browser/base/content/test/general/refresh_meta.sjs
@@ -0,0 +1,35 @@
+/**
+ * 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) {
+ 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 &lt;title&gt;
+ <title>
+
+
+ This is a title
+
+ </title>
+ </text>
+ <text id="text2" x="10px" y="96px" font-size="24px">
+ This contains only &lt;desc&gt;
+ <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 &lt;title&gt;
+ <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 &lt;title&gt;
+ <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 &lt;title&gt; &amp; 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
new file mode 100644
index 0000000000..ac7ece3519
--- /dev/null
+++ b/browser/base/content/test/general/video.ogg
Binary files differ
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
new file mode 100644
index 0000000000..093158432a
--- /dev/null
+++ b/browser/base/content/test/general/web_video1.ogv
Binary files differ
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
+