summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /browser/base/content/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'browser/base/content/test')
-rw-r--r--browser/base/content/test/alerts/.eslintrc.js7
-rw-r--r--browser/base/content/test/alerts/browser.ini12
-rw-r--r--browser/base/content/test/alerts/browser_notification_close.js71
-rw-r--r--browser/base/content/test/alerts/browser_notification_do_not_disturb.js80
-rw-r--r--browser/base/content/test/alerts/browser_notification_open_settings.js58
-rw-r--r--browser/base/content/test/alerts/browser_notification_permission_migration.js45
-rw-r--r--browser/base/content/test/alerts/browser_notification_remove_permission.js72
-rw-r--r--browser/base/content/test/alerts/browser_notification_replace.js38
-rw-r--r--browser/base/content/test/alerts/browser_notification_tab_switching.js80
-rw-r--r--browser/base/content/test/alerts/file_dom_notifications.html39
-rw-r--r--browser/base/content/test/alerts/head.js71
-rw-r--r--browser/base/content/test/captivePortal/browser.ini9
-rw-r--r--browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js119
-rw-r--r--browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js91
-rw-r--r--browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js82
-rw-r--r--browser/base/content/test/captivePortal/head.js181
-rw-r--r--browser/base/content/test/chrome/.eslintrc.js7
-rw-r--r--browser/base/content/test/chrome/chrome.ini3
-rw-r--r--browser/base/content/test/chrome/test_aboutCrashed.xul86
-rw-r--r--browser/base/content/test/general/.eslintrc.js8
-rw-r--r--browser/base/content/test/general/POSTSearchEngine.xml6
-rw-r--r--browser/base/content/test/general/aboutHome_content_script.js6
-rw-r--r--browser/base/content/test/general/accounts_testRemoteCommands.html83
-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/benignPage.html12
-rw-r--r--browser/base/content/test/general/browser.ini494
-rw-r--r--browser/base/content/test/general/browser_PageMetaData_pushstate.js29
-rw-r--r--browser/base/content/test/general/browser_aboutAccounts.js499
-rw-r--r--browser/base/content/test/general/browser_aboutCertError.js409
-rw-r--r--browser/base/content/test/general/browser_aboutHealthReport.js139
-rw-r--r--browser/base/content/test/general/browser_aboutHome.js668
-rw-r--r--browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js28
-rw-r--r--browser/base/content/test/general/browser_aboutNetError.js47
-rw-r--r--browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js26
-rw-r--r--browser/base/content/test/general/browser_accesskeys.js82
-rw-r--r--browser/base/content/test/general/browser_addCertException.js50
-rw-r--r--browser/base/content/test/general/browser_addKeywordSearch.js81
-rw-r--r--browser/base/content/test/general/browser_alltabslistener.js206
-rw-r--r--browser/base/content/test/general/browser_audioTabIcon.js504
-rw-r--r--browser/base/content/test/general/browser_backButtonFitts.js42
-rw-r--r--browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js76
-rw-r--r--browser/base/content/test/general/browser_blob-channelname.js11
-rw-r--r--browser/base/content/test/general/browser_blockHPKP.js101
-rw-r--r--browser/base/content/test/general/browser_bookmark_popup.js431
-rw-r--r--browser/base/content/test/general/browser_bookmark_titles.js98
-rw-r--r--browser/base/content/test/general/browser_bug1015721.js54
-rw-r--r--browser/base/content/test/general/browser_bug1045809.js68
-rw-r--r--browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js36
-rw-r--r--browser/base/content/test/general/browser_bug1261299.js73
-rw-r--r--browser/base/content/test/general/browser_bug1297539.js114
-rw-r--r--browser/base/content/test/general/browser_bug1299667.js71
-rw-r--r--browser/base/content/test/general/browser_bug321000.js80
-rw-r--r--browser/base/content/test/general/browser_bug356571.js93
-rw-r--r--browser/base/content/test/general/browser_bug380960.js11
-rw-r--r--browser/base/content/test/general/browser_bug386835.js89
-rw-r--r--browser/base/content/test/general/browser_bug406216.js54
-rw-r--r--browser/base/content/test/general/browser_bug408415.js45
-rw-r--r--browser/base/content/test/general/browser_bug409481.js83
-rw-r--r--browser/base/content/test/general/browser_bug409624.js57
-rw-r--r--browser/base/content/test/general/browser_bug413915.js62
-rw-r--r--browser/base/content/test/general/browser_bug416661.js43
-rw-r--r--browser/base/content/test/general/browser_bug417483.js30
-rw-r--r--browser/base/content/test/general/browser_bug419612.js32
-rw-r--r--browser/base/content/test/general/browser_bug422590.js50
-rw-r--r--browser/base/content/test/general/browser_bug423833.js138
-rw-r--r--browser/base/content/test/general/browser_bug424101.js52
-rw-r--r--browser/base/content/test/general/browser_bug427559.js38
-rw-r--r--browser/base/content/test/general/browser_bug431826.js50
-rw-r--r--browser/base/content/test/general/browser_bug432599.js127
-rw-r--r--browser/base/content/test/general/browser_bug435035.js17
-rw-r--r--browser/base/content/test/general/browser_bug435325.js69
-rw-r--r--browser/base/content/test/general/browser_bug441778.js46
-rw-r--r--browser/base/content/test/general/browser_bug455852.js20
-rw-r--r--browser/base/content/test/general/browser_bug460146.js51
-rw-r--r--browser/base/content/test/general/browser_bug462289.js81
-rw-r--r--browser/base/content/test/general/browser_bug462673.js36
-rw-r--r--browser/base/content/test/general/browser_bug477014.js25
-rw-r--r--browser/base/content/test/general/browser_bug479408.js17
-rw-r--r--browser/base/content/test/general/browser_bug479408_sample.html4
-rw-r--r--browser/base/content/test/general/browser_bug481560.js21
-rw-r--r--browser/base/content/test/general/browser_bug484315.js23
-rw-r--r--browser/base/content/test/general/browser_bug491431.js34
-rw-r--r--browser/base/content/test/general/browser_bug495058.js38
-rw-r--r--browser/base/content/test/general/browser_bug517902.js42
-rw-r--r--browser/base/content/test/general/browser_bug519216.js45
-rw-r--r--browser/base/content/test/general/browser_bug520538.js15
-rw-r--r--browser/base/content/test/general/browser_bug521216.js50
-rw-r--r--browser/base/content/test/general/browser_bug533232.js36
-rw-r--r--browser/base/content/test/general/browser_bug537013.js135
-rw-r--r--browser/base/content/test/general/browser_bug537474.js8
-rw-r--r--browser/base/content/test/general/browser_bug550565.js44
-rw-r--r--browser/base/content/test/general/browser_bug553455.js1200
-rw-r--r--browser/base/content/test/general/browser_bug555224.js40
-rw-r--r--browser/base/content/test/general/browser_bug555767.js54
-rw-r--r--browser/base/content/test/general/browser_bug559991.js42
-rw-r--r--browser/base/content/test/general/browser_bug561636.js370
-rw-r--r--browser/base/content/test/general/browser_bug563588.js30
-rw-r--r--browser/base/content/test/general/browser_bug565575.js14
-rw-r--r--browser/base/content/test/general/browser_bug567306.js50
-rw-r--r--browser/base/content/test/general/browser_bug575561.js97
-rw-r--r--browser/base/content/test/general/browser_bug575830.js33
-rw-r--r--browser/base/content/test/general/browser_bug577121.js29
-rw-r--r--browser/base/content/test/general/browser_bug578534.js23
-rw-r--r--browser/base/content/test/general/browser_bug579872.js28
-rw-r--r--browser/base/content/test/general/browser_bug580638.js60
-rw-r--r--browser/base/content/test/general/browser_bug580956.js26
-rw-r--r--browser/base/content/test/general/browser_bug581242.js21
-rw-r--r--browser/base/content/test/general/browser_bug581253.js86
-rw-r--r--browser/base/content/test/general/browser_bug585558.js153
-rw-r--r--browser/base/content/test/general/browser_bug585785.js35
-rw-r--r--browser/base/content/test/general/browser_bug585830.js25
-rw-r--r--browser/base/content/test/general/browser_bug590206.js163
-rw-r--r--browser/base/content/test/general/browser_bug592338.js163
-rw-r--r--browser/base/content/test/general/browser_bug594131.js21
-rw-r--r--browser/base/content/test/general/browser_bug595507.js36
-rw-r--r--browser/base/content/test/general/browser_bug596687.js25
-rw-r--r--browser/base/content/test/general/browser_bug597218.js38
-rw-r--r--browser/base/content/test/general/browser_bug609700.js20
-rw-r--r--browser/base/content/test/general/browser_bug623893.js37
-rw-r--r--browser/base/content/test/general/browser_bug624734.js29
-rw-r--r--browser/base/content/test/general/browser_bug633691.js28
-rw-r--r--browser/base/content/test/general/browser_bug647886.js40
-rw-r--r--browser/base/content/test/general/browser_bug655584.js23
-rw-r--r--browser/base/content/test/general/browser_bug664672.js19
-rw-r--r--browser/base/content/test/general/browser_bug676619.js124
-rw-r--r--browser/base/content/test/general/browser_bug678392-1.html12
-rw-r--r--browser/base/content/test/general/browser_bug678392-2.html12
-rw-r--r--browser/base/content/test/general/browser_bug678392.js191
-rw-r--r--browser/base/content/test/general/browser_bug710878.js34
-rw-r--r--browser/base/content/test/general/browser_bug719271.js95
-rw-r--r--browser/base/content/test/general/browser_bug724239.js11
-rw-r--r--browser/base/content/test/general/browser_bug734076.js114
-rw-r--r--browser/base/content/test/general/browser_bug735471.js23
-rw-r--r--browser/base/content/test/general/browser_bug749738.js29
-rw-r--r--browser/base/content/test/general/browser_bug763468_perwindowpb.js70
-rw-r--r--browser/base/content/test/general/browser_bug767836_perwindowpb.js90
-rw-r--r--browser/base/content/test/general/browser_bug817947.js55
-rw-r--r--browser/base/content/test/general/browser_bug822367.js187
-rw-r--r--browser/base/content/test/general/browser_bug832435.js23
-rw-r--r--browser/base/content/test/general/browser_bug839103.js120
-rw-r--r--browser/base/content/test/general/browser_bug882977.js29
-rw-r--r--browser/base/content/test/general/browser_bug902156.js174
-rw-r--r--browser/base/content/test/general/browser_bug906190.js240
-rw-r--r--browser/base/content/test/general/browser_bug963945.js23
-rw-r--r--browser/base/content/test/general/browser_bug970746.js121
-rw-r--r--browser/base/content/test/general/browser_bug970746.xhtml20
-rw-r--r--browser/base/content/test/general/browser_clipboard.js174
-rw-r--r--browser/base/content/test/general/browser_clipboard_pastefile.js62
-rw-r--r--browser/base/content/test/general/browser_contentAltClick.js107
-rw-r--r--browser/base/content/test/general/browser_contentAreaClick.js307
-rw-r--r--browser/base/content/test/general/browser_contentSearchUI.js771
-rw-r--r--browser/base/content/test/general/browser_contextmenu.js996
-rw-r--r--browser/base/content/test/general/browser_contextmenu_childprocess.js84
-rw-r--r--browser/base/content/test/general/browser_contextmenu_input.js243
-rw-r--r--browser/base/content/test/general/browser_csp_block_all_mixedcontent.js55
-rw-r--r--browser/base/content/test/general/browser_ctrlTab.js185
-rw-r--r--browser/base/content/test/general/browser_datachoices_notification.js221
-rw-r--r--browser/base/content/test/general/browser_decoderDoctor.js122
-rw-r--r--browser/base/content/test/general/browser_devedition.js129
-rw-r--r--browser/base/content/test/general/browser_discovery.js162
-rw-r--r--browser/base/content/test/general/browser_documentnavigation.js266
-rw-r--r--browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js221
-rw-r--r--browser/base/content/test/general/browser_double_close_tab.js80
-rw-r--r--browser/base/content/test/general/browser_drag.js45
-rw-r--r--browser/base/content/test/general/browser_duplicateIDs.js8
-rw-r--r--browser/base/content/test/general/browser_e10s_about_process.js114
-rw-r--r--browser/base/content/test/general/browser_e10s_chrome_process.js150
-rw-r--r--browser/base/content/test/general/browser_e10s_javascript.js11
-rw-r--r--browser/base/content/test/general/browser_e10s_switchbrowser.js261
-rw-r--r--browser/base/content/test/general/browser_favicon_change.js41
-rw-r--r--browser/base/content/test/general/browser_favicon_change_not_in_document.js34
-rw-r--r--browser/base/content/test/general/browser_feed_discovery.js33
-rw-r--r--browser/base/content/test/general/browser_findbarClose.js35
-rw-r--r--browser/base/content/test/general/browser_focusonkeydown.js26
-rw-r--r--browser/base/content/test/general/browser_fullscreen-window-open.js347
-rw-r--r--browser/base/content/test/general/browser_fxa_migrate.js18
-rw-r--r--browser/base/content/test/general/browser_fxa_oauth.html30
-rw-r--r--browser/base/content/test/general/browser_fxa_oauth.js327
-rw-r--r--browser/base/content/test/general/browser_fxa_oauth_with_keys.html33
-rw-r--r--browser/base/content/test/general/browser_fxa_web_channel.html138
-rw-r--r--browser/base/content/test/general/browser_fxa_web_channel.js210
-rw-r--r--browser/base/content/test/general/browser_fxaccounts.js261
-rw-r--r--browser/base/content/test/general/browser_gZipOfflineChild.js80
-rw-r--r--browser/base/content/test/general/browser_gestureSupport.js670
-rw-r--r--browser/base/content/test/general/browser_getshortcutoruri.js143
-rw-r--r--browser/base/content/test/general/browser_hide_removing.js39
-rw-r--r--browser/base/content/test/general/browser_homeDrop.js90
-rw-r--r--browser/base/content/test/general/browser_identity_UI.js146
-rw-r--r--browser/base/content/test/general/browser_insecureLoginForms.js162
-rw-r--r--browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js39
-rw-r--r--browser/base/content/test/general/browser_keywordBookmarklets.js54
-rw-r--r--browser/base/content/test/general/browser_keywordSearch.js88
-rw-r--r--browser/base/content/test/general/browser_keywordSearch_postData.js94
-rw-r--r--browser/base/content/test/general/browser_lastAccessedTab.js47
-rw-r--r--browser/base/content/test/general/browser_mcb_redirect.js314
-rw-r--r--browser/base/content/test/general/browser_menuButtonBadgeManager.js46
-rw-r--r--browser/base/content/test/general/browser_menuButtonFitts.js32
-rw-r--r--browser/base/content/test/general/browser_middleMouse_noJSPaste.js34
-rw-r--r--browser/base/content/test/general/browser_minimize.js18
-rw-r--r--browser/base/content/test/general/browser_misused_characters_in_strings.js244
-rw-r--r--browser/base/content/test/general/browser_mixedContentFramesOnHttp.js34
-rw-r--r--browser/base/content/test/general/browser_mixedContentFromOnunload.js49
-rw-r--r--browser/base/content/test/general/browser_mixed_content_cert_override.js54
-rw-r--r--browser/base/content/test/general/browser_mixedcontent_securityflags.js70
-rw-r--r--browser/base/content/test/general/browser_modifiedclick_inherit_principal.js30
-rw-r--r--browser/base/content/test/general/browser_newTabDrop.js99
-rw-r--r--browser/base/content/test/general/browser_newWindowDrop.js120
-rw-r--r--browser/base/content/test/general/browser_newwindow_focus.js96
-rw-r--r--browser/base/content/test/general/browser_no_mcb_on_http_site.js106
-rw-r--r--browser/base/content/test/general/browser_offlineQuotaNotification.js95
-rw-r--r--browser/base/content/test/general/browser_overflowScroll.js91
-rw-r--r--browser/base/content/test/general/browser_pageInfo.js38
-rw-r--r--browser/base/content/test/general/browser_page_style_menu.js97
-rw-r--r--browser/base/content/test/general/browser_page_style_menu_update.js67
-rw-r--r--browser/base/content/test/general/browser_pageinfo_svg_image.js38
-rw-r--r--browser/base/content/test/general/browser_parsable_css.js376
-rw-r--r--browser/base/content/test/general/browser_parsable_script.js132
-rw-r--r--browser/base/content/test/general/browser_permissions.js202
-rw-r--r--browser/base/content/test/general/browser_pinnedTabs.js75
-rw-r--r--browser/base/content/test/general/browser_plainTextLinks.js146
-rw-r--r--browser/base/content/test/general/browser_printpreview.js74
-rw-r--r--browser/base/content/test/general/browser_private_browsing_window.js65
-rw-r--r--browser/base/content/test/general/browser_private_no_prompt.js12
-rw-r--r--browser/base/content/test/general/browser_purgehistory_clears_sh.js60
-rw-r--r--browser/base/content/test/general/browser_refreshBlocker.js135
-rw-r--r--browser/base/content/test/general/browser_registerProtocolHandler_notification.html15
-rw-r--r--browser/base/content/test/general/browser_registerProtocolHandler_notification.js43
-rw-r--r--browser/base/content/test/general/browser_relatedTabs.js51
-rw-r--r--browser/base/content/test/general/browser_remoteTroubleshoot.js93
-rw-r--r--browser/base/content/test/general/browser_remoteWebNavigation_postdata.js50
-rw-r--r--browser/base/content/test/general/browser_removeTabsToTheEnd.js24
-rw-r--r--browser/base/content/test/general/browser_restore_isAppTab.js160
-rw-r--r--browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js39
-rw-r--r--browser/base/content/test/general/browser_sanitize-sitepermissions.js52
-rw-r--r--browser/base/content/test/general/browser_sanitize-timespans.js733
-rw-r--r--browser/base/content/test/general/browser_sanitizeDialog.js1027
-rw-r--r--browser/base/content/test/general/browser_save_link-perwindowpb.js199
-rw-r--r--browser/base/content/test/general/browser_save_link_when_window_navigates.js173
-rw-r--r--browser/base/content/test/general/browser_save_private_link_perwindowpb.js116
-rw-r--r--browser/base/content/test/general/browser_save_video.js87
-rw-r--r--browser/base/content/test/general/browser_save_video_frame.js125
-rw-r--r--browser/base/content/test/general/browser_scope.js10
-rw-r--r--browser/base/content/test/general/browser_selectTabAtIndex.js81
-rw-r--r--browser/base/content/test/general/browser_selectpopup.js563
-rw-r--r--browser/base/content/test/general/browser_ssl_error_reports.js174
-rw-r--r--browser/base/content/test/general/browser_star_hsts.js85
-rw-r--r--browser/base/content/test/general/browser_star_hsts.sjs13
-rw-r--r--browser/base/content/test/general/browser_subframe_favicons_not_used.js20
-rw-r--r--browser/base/content/test/general/browser_syncui.js205
-rw-r--r--browser/base/content/test/general/browser_tabDrop.js103
-rw-r--r--browser/base/content/test/general/browser_tabReorder.js49
-rw-r--r--browser/base/content/test/general/browser_tab_close_dependent_window.js24
-rw-r--r--browser/base/content/test/general/browser_tab_detach_restore.js34
-rw-r--r--browser/base/content/test/general/browser_tab_drag_drop_perwindow.js216
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop.js186
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop2.js57
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul169
-rw-r--r--browser/base/content/test/general/browser_tabbar_big_widgets.js29
-rw-r--r--browser/base/content/test/general/browser_tabfocus.js565
-rw-r--r--browser/base/content/test/general/browser_tabkeynavigation.js156
-rw-r--r--browser/base/content/test/general/browser_tabopen_reflows.js157
-rw-r--r--browser/base/content/test/general/browser_tabs_close_beforeunload.js49
-rw-r--r--browser/base/content/test/general/browser_tabs_isActive.js152
-rw-r--r--browser/base/content/test/general/browser_tabs_owner.js44
-rw-r--r--browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js126
-rw-r--r--browser/base/content/test/general/browser_trackingUI_1.js170
-rw-r--r--browser/base/content/test/general/browser_trackingUI_2.js96
-rw-r--r--browser/base/content/test/general/browser_trackingUI_3.js52
-rw-r--r--browser/base/content/test/general/browser_trackingUI_4.js109
-rw-r--r--browser/base/content/test/general/browser_trackingUI_5.js131
-rw-r--r--browser/base/content/test/general/browser_trackingUI_6.js46
-rw-r--r--browser/base/content/test/general/browser_trackingUI_telemetry.js145
-rw-r--r--browser/base/content/test/general/browser_typeAheadFind.js22
-rw-r--r--browser/base/content/test/general/browser_unknownContentType_title.js33
-rw-r--r--browser/base/content/test/general/browser_unloaddialogs.js41
-rw-r--r--browser/base/content/test/general/browser_utilityOverlay.js112
-rw-r--r--browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js55
-rw-r--r--browser/base/content/test/general/browser_visibleFindSelection.js52
-rw-r--r--browser/base/content/test/general/browser_visibleTabs.js97
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js34
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js66
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_contextMenu.js72
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_tabPreview.js41
-rw-r--r--browser/base/content/test/general/browser_web_channel.html189
-rw-r--r--browser/base/content/test/general/browser_web_channel.js436
-rw-r--r--browser/base/content/test/general/browser_web_channel_iframe.html96
-rw-r--r--browser/base/content/test/general/browser_windowactivation.js183
-rw-r--r--browser/base/content/test/general/browser_windowopen_reflows.js117
-rw-r--r--browser/base/content/test/general/browser_zbug569342.js80
-rw-r--r--browser/base/content/test/general/bug1262648_string_with_newlines.dtd3
-rw-r--r--browser/base/content/test/general/bug364677-data.xml5
-rw-r--r--browser/base/content/test/general/bug364677-data.xml^headers^1
-rw-r--r--browser/base/content/test/general/bug395533-data.txt6
-rw-r--r--browser/base/content/test/general/bug592338.html24
-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/bug839103.css1
-rw-r--r--browser/base/content/test/general/clipboard_pastefile.html37
-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/contentSearchUI.html21
-rw-r--r--browser/base/content/test/general/contentSearchUI.js209
-rw-r--r--browser/base/content/test/general/content_aboutAccounts.js87
-rw-r--r--browser/base/content/test/general/contextmenu_common.js324
-rw-r--r--browser/base/content/test/general/ctxmenu-image.pngbin0 -> 5401 bytes
-rw-r--r--browser/base/content/test/general/discovery.html8
-rw-r--r--browser/base/content/test/general/download_page.html47
-rw-r--r--browser/base/content/test/general/dummy_page.html9
-rw-r--r--browser/base/content/test/general/feed_discovery.html73
-rw-r--r--browser/base/content/test/general/feed_tab.html17
-rw-r--r--browser/base/content/test/general/file_bug1045809_1.html7
-rw-r--r--browser/base/content/test/general/file_bug1045809_2.html7
-rw-r--r--browser/base/content/test/general/file_bug822367_1.html18
-rw-r--r--browser/base/content/test/general/file_bug822367_1.js1
-rw-r--r--browser/base/content/test/general/file_bug822367_2.html16
-rw-r--r--browser/base/content/test/general/file_bug822367_3.html27
-rw-r--r--browser/base/content/test/general/file_bug822367_4.html18
-rw-r--r--browser/base/content/test/general/file_bug822367_4.js1
-rw-r--r--browser/base/content/test/general/file_bug822367_4B.html18
-rw-r--r--browser/base/content/test/general/file_bug822367_5.html24
-rw-r--r--browser/base/content/test/general/file_bug822367_6.html16
-rw-r--r--browser/base/content/test/general/file_bug902156.js5
-rw-r--r--browser/base/content/test/general/file_bug902156_1.html15
-rw-r--r--browser/base/content/test/general/file_bug902156_2.html17
-rw-r--r--browser/base/content/test/general/file_bug902156_3.html15
-rw-r--r--browser/base/content/test/general/file_bug906190.js5
-rw-r--r--browser/base/content/test/general/file_bug906190.sjs17
-rw-r--r--browser/base/content/test/general/file_bug906190_1.html15
-rw-r--r--browser/base/content/test/general/file_bug906190_2.html15
-rw-r--r--browser/base/content/test/general/file_bug906190_3_4.html14
-rw-r--r--browser/base/content/test/general/file_bug906190_redirected.html15
-rw-r--r--browser/base/content/test/general/file_bug970276_favicon1.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/general/file_bug970276_favicon2.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/general/file_bug970276_popup1.html14
-rw-r--r--browser/base/content/test/general/file_bug970276_popup2.html12
-rw-r--r--browser/base/content/test/general/file_csp_block_all_mixedcontent.html9
-rw-r--r--browser/base/content/test/general/file_csp_block_all_mixedcontent.js3
-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_favicon_change.html13
-rw-r--r--browser/base/content/test/general/file_favicon_change_not_in_document.html21
-rw-r--r--browser/base/content/test/general/file_fullscreen-window-open.html24
-rw-r--r--browser/base/content/test/general/file_generic_favicon.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/general/file_mediaPlayback.html2
-rw-r--r--browser/base/content/test/general/file_mixedContentFramesOnHttp.html14
-rw-r--r--browser/base/content/test/general/file_mixedContentFromOnunload.html18
-rw-r--r--browser/base/content/test/general/file_mixedContentFromOnunload_test1.html14
-rw-r--r--browser/base/content/test/general/file_mixedContentFromOnunload_test2.html15
-rw-r--r--browser/base/content/test/general/file_mixedPassiveContent.html13
-rw-r--r--browser/base/content/test/general/file_trackingUI_6.html16
-rw-r--r--browser/base/content/test/general/file_trackingUI_6.js2
-rw-r--r--browser/base/content/test/general/file_trackingUI_6.js^headers^1
-rw-r--r--browser/base/content/test/general/file_with_favicon.html12
-rw-r--r--browser/base/content/test/general/fxa_profile_handler.sjs34
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.cacheManifest2
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.htmlbin0 -> 303 bytes
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.html^headers^2
-rw-r--r--browser/base/content/test/general/gZipOfflineChild_uncompressed.html21
-rw-r--r--browser/base/content/test/general/head.js1069
-rw-r--r--browser/base/content/test/general/head_plain.js27
-rw-r--r--browser/base/content/test/general/healthreport_pingData.js17
-rw-r--r--browser/base/content/test/general/healthreport_testRemoteCommands.html243
-rw-r--r--browser/base/content/test/general/insecure_opener.html9
-rw-r--r--browser/base/content/test/general/mochitest.ini27
-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/offlineByDefault.js17
-rw-r--r--browser/base/content/test/general/offlineChild.cacheManifest2
-rw-r--r--browser/base/content/test/general/offlineChild.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/offlineChild.html20
-rw-r--r--browser/base/content/test/general/offlineChild2.cacheManifest2
-rw-r--r--browser/base/content/test/general/offlineChild2.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/offlineChild2.html20
-rw-r--r--browser/base/content/test/general/offlineEvent.cacheManifest2
-rw-r--r--browser/base/content/test/general/offlineEvent.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/offlineEvent.html9
-rw-r--r--browser/base/content/test/general/offlineQuotaNotification.cacheManifest7
-rw-r--r--browser/base/content/test/general/offlineQuotaNotification.html9
-rw-r--r--browser/base/content/test/general/page_style_sample.html41
-rw-r--r--browser/base/content/test/general/parsingTestHelpers.jsm131
-rw-r--r--browser/base/content/test/general/permissions.html14
-rw-r--r--browser/base/content/test/general/pinning_headers.sjs23
-rw-r--r--browser/base/content/test/general/print_postdata.sjs22
-rw-r--r--browser/base/content/test/general/refresh_header.sjs24
-rw-r--r--browser/base/content/test/general/refresh_meta.sjs36
-rw-r--r--browser/base/content/test/general/searchSuggestionEngine.sjs9
-rw-r--r--browser/base/content/test/general/searchSuggestionEngine.xml9
-rw-r--r--browser/base/content/test/general/searchSuggestionEngine2.xml9
-rw-r--r--browser/base/content/test/general/ssl_error_reports.sjs91
-rw-r--r--browser/base/content/test/general/subtst_contextmenu.html73
-rw-r--r--browser/base/content/test/general/subtst_contextmenu_input.html29
-rw-r--r--browser/base/content/test/general/subtst_contextmenu_xul.xul9
-rw-r--r--browser/base/content/test/general/svg_image.html11
-rw-r--r--browser/base/content/test/general/test-mixedcontent-securityerrors.html21
-rw-r--r--browser/base/content/test/general/test_bug364677.html32
-rw-r--r--browser/base/content/test/general/test_bug395533.html38
-rw-r--r--browser/base/content/test/general/test_bug435035.html1
-rw-r--r--browser/base/content/test/general/test_bug462673.html18
-rw-r--r--browser/base/content/test/general/test_bug628179.html10
-rw-r--r--browser/base/content/test/general/test_bug839103.html10
-rw-r--r--browser/base/content/test/general/test_bug959531.html9
-rw-r--r--browser/base/content/test/general/test_mcb_double_redirect_image.html23
-rw-r--r--browser/base/content/test/general/test_mcb_redirect.html15
-rw-r--r--browser/base/content/test/general/test_mcb_redirect.js5
-rw-r--r--browser/base/content/test/general/test_mcb_redirect.sjs22
-rw-r--r--browser/base/content/test/general/test_mcb_redirect_image.html23
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font.css10
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font.html47
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font2.css1
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font2.html48
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_img.css3
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_img.html47
-rw-r--r--browser/base/content/test/general/test_offlineNotification.html129
-rw-r--r--browser/base/content/test/general/test_offline_gzip.html21
-rw-r--r--browser/base/content/test/general/test_process_flags_chrome.html10
-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/trackingPage.html12
-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
-rw-r--r--browser/base/content/test/general/zoom_test.html14
-rw-r--r--browser/base/content/test/newtab/.eslintrc.js7
-rw-r--r--browser/base/content/test/newtab/browser.ini55
-rw-r--r--browser/base/content/test/newtab/browser_newtab_1188015.js26
-rw-r--r--browser/base/content/test/newtab/browser_newtab_background_captures.js64
-rw-r--r--browser/base/content/test/newtab/browser_newtab_block.js95
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1145428.js87
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1178586.js83
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1194895.js146
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1271075.js32
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug721442.js28
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug722273.js73
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug723102.js24
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug723121.js42
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug725996.js35
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug734043.js34
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug735987.js32
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug752841.js56
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug765628.js32
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug876313.js24
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug991111.js35
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug991210.js34
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug998387.js39
-rw-r--r--browser/base/content/test/newtab/browser_newtab_disable.js49
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drag_drop.js95
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js63
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drop_preview.js41
-rw-r--r--browser/base/content/test/newtab/browser_newtab_enhanced.js228
-rw-r--r--browser/base/content/test/newtab/browser_newtab_focus.js48
-rw-r--r--browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js56
-rw-r--r--browser/base/content/test/newtab/browser_newtab_reflow_load.js37
-rw-r--r--browser/base/content/test/newtab/browser_newtab_reportLinkAction.js83
-rw-r--r--browser/base/content/test/newtab/browser_newtab_search.js247
-rw-r--r--browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js53
-rw-r--r--browser/base/content/test/newtab/browser_newtab_undo.js47
-rw-r--r--browser/base/content/test/newtab/browser_newtab_unpin.js56
-rw-r--r--browser/base/content/test/newtab/browser_newtab_update.js48
-rw-r--r--browser/base/content/test/newtab/content-reflows.js26
-rw-r--r--browser/base/content/test/newtab/head.js552
-rw-r--r--browser/base/content/test/newtab/searchEngine1x2xLogo.xml9
-rw-r--r--browser/base/content/test/newtab/searchEngine1xLogo.xml7
-rw-r--r--browser/base/content/test/newtab/searchEngine2xLogo.xml7
-rw-r--r--browser/base/content/test/newtab/searchEngineFavicon.xml6
-rw-r--r--browser/base/content/test/newtab/searchEngineNoLogo.xml5
-rw-r--r--browser/base/content/test/plugins/.eslintrc.js7
-rw-r--r--browser/base/content/test/plugins/blockNoPlugins.xml7
-rw-r--r--browser/base/content/test/plugins/blockPluginHard.xml11
-rw-r--r--browser/base/content/test/plugins/blockPluginInfoURL.xml12
-rw-r--r--browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml11
-rw-r--r--browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml11
-rw-r--r--browser/base/content/test/plugins/blocklist_proxy.js78
-rw-r--r--browser/base/content/test/plugins/browser.ini78
-rw-r--r--browser/base/content/test/plugins/browser_CTP_context_menu.js69
-rw-r--r--browser/base/content/test/plugins/browser_CTP_crashreporting.js233
-rw-r--r--browser/base/content/test/plugins/browser_CTP_data_urls.js255
-rw-r--r--browser/base/content/test/plugins/browser_CTP_drag_drop.js96
-rw-r--r--browser/base/content/test/plugins/browser_CTP_hide_overlay.js88
-rw-r--r--browser/base/content/test/plugins/browser_CTP_iframe.js48
-rw-r--r--browser/base/content/test/plugins/browser_CTP_multi_allow.js99
-rw-r--r--browser/base/content/test/plugins/browser_CTP_nonplugins.js58
-rw-r--r--browser/base/content/test/plugins/browser_CTP_notificationBar.js151
-rw-r--r--browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js120
-rw-r--r--browser/base/content/test/plugins/browser_CTP_remove_navigate.js79
-rw-r--r--browser/base/content/test/plugins/browser_CTP_resize.js130
-rw-r--r--browser/base/content/test/plugins/browser_CTP_zoom.js62
-rw-r--r--browser/base/content/test/plugins/browser_blocking.js349
-rw-r--r--browser/base/content/test/plugins/browser_blocklist_content.js104
-rw-r--r--browser/base/content/test/plugins/browser_bug743421.js119
-rw-r--r--browser/base/content/test/plugins/browser_bug744745.js50
-rw-r--r--browser/base/content/test/plugins/browser_bug787619.js65
-rw-r--r--browser/base/content/test/plugins/browser_bug797677.js43
-rw-r--r--browser/base/content/test/plugins/browser_bug812562.js80
-rw-r--r--browser/base/content/test/plugins/browser_bug818118.js40
-rw-r--r--browser/base/content/test/plugins/browser_bug820497.js71
-rw-r--r--browser/base/content/test/plugins/browser_clearplugindata.html30
-rw-r--r--browser/base/content/test/plugins/browser_clearplugindata.js127
-rw-r--r--browser/base/content/test/plugins/browser_clearplugindata_noage.html30
-rw-r--r--browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js34
-rw-r--r--browser/base/content/test/plugins/browser_pageInfo_plugins.js191
-rw-r--r--browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js207
-rw-r--r--browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js254
-rw-r--r--browser/base/content/test/plugins/browser_plugin_reloading.js85
-rw-r--r--browser/base/content/test/plugins/browser_pluginnotification.js626
-rw-r--r--browser/base/content/test/plugins/browser_plugins_added_dynamically.js137
-rw-r--r--browser/base/content/test/plugins/browser_private_clicktoplay.js216
-rw-r--r--browser/base/content/test/plugins/head.js396
-rw-r--r--browser/base/content/test/plugins/plugin_add_dynamically.html18
-rw-r--r--browser/base/content/test/plugins/plugin_alternate_content.html9
-rw-r--r--browser/base/content/test/plugins/plugin_big.html9
-rw-r--r--browser/base/content/test/plugins/plugin_both.html10
-rw-r--r--browser/base/content/test/plugins/plugin_both2.html10
-rw-r--r--browser/base/content/test/plugins/plugin_bug744745.html12
-rw-r--r--browser/base/content/test/plugins/plugin_bug749455.html8
-rw-r--r--browser/base/content/test/plugins/plugin_bug787619.html9
-rw-r--r--browser/base/content/test/plugins/plugin_bug797677.html5
-rw-r--r--browser/base/content/test/plugins/plugin_bug820497.html17
-rw-r--r--browser/base/content/test/plugins/plugin_clickToPlayAllow.html9
-rw-r--r--browser/base/content/test/plugins/plugin_clickToPlayDeny.html9
-rw-r--r--browser/base/content/test/plugins/plugin_crashCommentAndURL.html27
-rw-r--r--browser/base/content/test/plugins/plugin_data_url.html11
-rw-r--r--browser/base/content/test/plugins/plugin_hidden_to_visible.html11
-rw-r--r--browser/base/content/test/plugins/plugin_iframe.html9
-rw-r--r--browser/base/content/test/plugins/plugin_outsideScrollArea.html25
-rw-r--r--browser/base/content/test/plugins/plugin_overlayed.html27
-rw-r--r--browser/base/content/test/plugins/plugin_positioned.html12
-rw-r--r--browser/base/content/test/plugins/plugin_small.html9
-rw-r--r--browser/base/content/test/plugins/plugin_small_2.html9
-rw-r--r--browser/base/content/test/plugins/plugin_syncRemoved.html15
-rw-r--r--browser/base/content/test/plugins/plugin_test.html9
-rw-r--r--browser/base/content/test/plugins/plugin_test2.html10
-rw-r--r--browser/base/content/test/plugins/plugin_test3.html9
-rw-r--r--browser/base/content/test/plugins/plugin_two_types.html9
-rw-r--r--browser/base/content/test/plugins/plugin_unknown.html9
-rw-r--r--browser/base/content/test/plugins/plugin_zoom.html10
-rw-r--r--browser/base/content/test/popupNotifications/.eslintrc.js7
-rw-r--r--browser/base/content/test/popupNotifications/browser.ini18
-rw-r--r--browser/base/content/test/popupNotifications/browser_displayURI.js28
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification.js203
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_2.js266
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_3.js305
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_4.js294
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js211
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js74
-rw-r--r--browser/base/content/test/popupNotifications/browser_reshow_in_background.js52
-rw-r--r--browser/base/content/test/popupNotifications/head.js303
-rw-r--r--browser/base/content/test/popups/browser.ini4
-rw-r--r--browser/base/content/test/popups/browser_popupUI.js37
-rw-r--r--browser/base/content/test/popups/browser_popup_blocker.js96
-rw-r--r--browser/base/content/test/popups/popup_blocker.html13
-rw-r--r--browser/base/content/test/referrer/.eslintrc.js7
-rw-r--r--browser/base/content/test/referrer/browser.ini24
-rw-r--r--browser/base/content/test/referrer/browser_referrer_middle_click.js20
-rw-r--r--browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js27
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js59
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js31
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js63
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_private.js22
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js21
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_window.js22
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js32
-rw-r--r--browser/base/content/test/referrer/browser_referrer_simple_click.js20
-rw-r--r--browser/base/content/test/referrer/file_referrer_policyserver.sjs37
-rw-r--r--browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs36
-rw-r--r--browser/base/content/test/referrer/file_referrer_testserver.sjs31
-rw-r--r--browser/base/content/test/referrer/head.js265
-rw-r--r--browser/base/content/test/siteIdentity/browser.ini8
-rw-r--r--browser/base/content/test/siteIdentity/browser_identityBlock_focus.js62
-rw-r--r--browser/base/content/test/siteIdentity/browser_identityPopup_focus.js27
-rw-r--r--browser/base/content/test/siteIdentity/head.js6
-rw-r--r--browser/base/content/test/social/.eslintrc.js7
-rw-r--r--browser/base/content/test/social/blocklist.xml6
-rw-r--r--browser/base/content/test/social/browser.ini23
-rw-r--r--browser/base/content/test/social/browser_aboutHome_activation.js229
-rw-r--r--browser/base/content/test/social/browser_addons.js217
-rw-r--r--browser/base/content/test/social/browser_blocklist.js211
-rw-r--r--browser/base/content/test/social/browser_share.js396
-rw-r--r--browser/base/content/test/social/browser_social_activation.js270
-rw-r--r--browser/base/content/test/social/head.js273
-rw-r--r--browser/base/content/test/social/microformats.html18
-rw-r--r--browser/base/content/test/social/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/social/opengraph/og_invalid_url.html11
-rw-r--r--browser/base/content/test/social/opengraph/opengraph.html13
-rw-r--r--browser/base/content/test/social/opengraph/shortlink_linkrel.html10
-rw-r--r--browser/base/content/test/social/opengraph/shorturl_link.html10
-rw-r--r--browser/base/content/test/social/opengraph/shorturl_linkrel.html25
-rw-r--r--browser/base/content/test/social/share.html9
-rw-r--r--browser/base/content/test/social/share_activate.html35
-rw-r--r--browser/base/content/test/social/social_activate.html41
-rw-r--r--browser/base/content/test/social/social_activate_basic.html41
-rw-r--r--browser/base/content/test/social/social_activate_iframe.html11
-rw-r--r--browser/base/content/test/social/social_crash_content_helper.js31
-rw-r--r--browser/base/content/test/social/social_postActivation.html12
-rw-r--r--browser/base/content/test/tabPrompts/.eslintrc.js7
-rw-r--r--browser/base/content/test/tabPrompts/browser.ini4
-rw-r--r--browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js41
-rw-r--r--browser/base/content/test/tabPrompts/browser_multiplePrompts.js72
-rw-r--r--browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js66
-rw-r--r--browser/base/content/test/tabPrompts/openPromptOffTimeout.html10
-rw-r--r--browser/base/content/test/tabcrashed/browser.ini13
-rw-r--r--browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js152
-rw-r--r--browser/base/content/test/tabcrashed/browser_clearEmail.js85
-rw-r--r--browser/base/content/test/tabcrashed/browser_showForm.js40
-rw-r--r--browser/base/content/test/tabcrashed/browser_shown.js203
-rw-r--r--browser/base/content/test/tabcrashed/browser_withoutDump.js36
-rw-r--r--browser/base/content/test/tabcrashed/head.js110
-rw-r--r--browser/base/content/test/tabs/.eslintrc.js7
-rw-r--r--browser/base/content/test/tabs/browser.ini4
-rw-r--r--browser/base/content/test/tabs/browser_tabSpinnerProbe.js93
-rw-r--r--browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js29
-rw-r--r--browser/base/content/test/urlbar/.eslintrc.js7
-rw-r--r--browser/base/content/test/urlbar/authenticate.sjs220
-rw-r--r--browser/base/content/test/urlbar/browser.ini101
-rw-r--r--browser/base/content/test/urlbar/browser_URLBarSetURI.js100
-rw-r--r--browser/base/content/test/urlbar/browser_action_keyword.js119
-rw-r--r--browser/base/content/test/urlbar/browser_action_keyword_override.js40
-rw-r--r--browser/base/content/test/urlbar/browser_action_searchengine.js36
-rw-r--r--browser/base/content/test/urlbar/browser_action_searchengine_alias.js35
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js57
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_autoselect.js92
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_cursor.js17
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js48
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_enter_race.js122
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_no_title.js15
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js102
-rw-r--r--browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js61
-rw-r--r--browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js37
-rw-r--r--browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js124
-rw-r--r--browser/base/content/test/urlbar/browser_bug1070778.js55
-rw-r--r--browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js29
-rw-r--r--browser/base/content/test/urlbar/browser_bug1225194-remotetab.js16
-rw-r--r--browser/base/content/test/urlbar/browser_bug304198.js109
-rw-r--r--browser/base/content/test/urlbar/browser_bug556061.js98
-rw-r--r--browser/base/content/test/urlbar/browser_bug562649.js24
-rw-r--r--browser/base/content/test/urlbar/browser_bug623155.js137
-rw-r--r--browser/base/content/test/urlbar/browser_bug783614.js13
-rw-r--r--browser/base/content/test/urlbar/browser_canonizeURL.js42
-rw-r--r--browser/base/content/test/urlbar/browser_dragdropURL.js15
-rw-r--r--browser/base/content/test/urlbar/browser_locationBarCommand.js218
-rw-r--r--browser/base/content/test/urlbar/browser_locationBarExternalLoad.js65
-rw-r--r--browser/base/content/test/urlbar/browser_moz_action_link.js31
-rw-r--r--browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js49
-rw-r--r--browser/base/content/test/urlbar/browser_search_favicon.js52
-rw-r--r--browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js216
-rw-r--r--browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js84
-rw-r--r--browser/base/content/test/urlbar/browser_urlHighlight.js134
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js104
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js49
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarCopying.js232
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarDecode.js97
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarDelete.js39
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarEnter.js45
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js69
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js17
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js111
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js49
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarOneOffs.js232
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js41
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js57
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarRevert.js37
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js198
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js66
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js254
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js216
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarStop.js30
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarTrimURLs.js98
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js17
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js146
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_blanking.js35
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js41
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js39
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_searchsettings.js30
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_stop_pending.js138
-rw-r--r--browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js31
-rw-r--r--browser/base/content/test/urlbar/dummy_page.html9
-rw-r--r--browser/base/content/test/urlbar/file_blank_but_not_blank.html2
-rw-r--r--browser/base/content/test/urlbar/file_urlbar_edit_dos.html23
-rw-r--r--browser/base/content/test/urlbar/head.js205
-rw-r--r--browser/base/content/test/urlbar/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/urlbar/print_postdata.sjs22
-rw-r--r--browser/base/content/test/urlbar/redirect_bug623155.sjs16
-rw-r--r--browser/base/content/test/urlbar/searchSuggestionEngine.sjs9
-rw-r--r--browser/base/content/test/urlbar/searchSuggestionEngine.xml9
-rw-r--r--browser/base/content/test/urlbar/slow-page.sjs22
-rw-r--r--browser/base/content/test/urlbar/test_wyciwyg_copying.html13
-rw-r--r--browser/base/content/test/webrtc/.eslintrc.js7
-rw-r--r--browser/base/content/test/webrtc/browser.ini11
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media.js554
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js109
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js266
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js109
-rw-r--r--browser/base/content/test/webrtc/get_user_media.html55
-rw-r--r--browser/base/content/test/webrtc/get_user_media_content_script.js85
-rw-r--r--browser/base/content/test/webrtc/head.js453
702 files changed, 57478 insertions, 0 deletions
diff --git a/browser/base/content/test/alerts/.eslintrc.js b/browser/base/content/test/alerts/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/alerts/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/alerts/browser.ini b/browser/base/content/test/alerts/browser.ini
new file mode 100644
index 000000000..07fcf5253
--- /dev/null
+++ b/browser/base/content/test/alerts/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ head.js
+ file_dom_notifications.html
+
+[browser_notification_close.js]
+[browser_notification_do_not_disturb.js]
+[browser_notification_open_settings.js]
+[browser_notification_remove_permission.js]
+[browser_notification_permission_migration.js]
+[browser_notification_replace.js]
+[browser_notification_tab_switching.js]
diff --git a/browser/base/content/test/alerts/browser_notification_close.js b/browser/base/content/test/alerts/browser_notification_close.js
new file mode 100644
index 000000000..bbd444212
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_close.js
@@ -0,0 +1,71 @@
+"use strict";
+
+const {PlacesTestUtils} =
+ Cu.import("resource://testing-common/PlacesTestUtils.jsm", {});
+
+let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+let oldShowFavicons;
+
+add_task(function* test_notificationClose() {
+ let pm = Services.perms;
+ let notificationURI = makeURI(notificationURL);
+ pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION);
+
+ oldShowFavicons = Services.prefs.getBoolPref("alerts.showFavicons");
+ Services.prefs.setBoolPref("alerts.showFavicons", true);
+
+ yield PlacesTestUtils.addVisits(notificationURI);
+ let faviconURI = yield new Promise(resolve => {
+ let faviconURI = makeURI("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC");
+ PlacesUtils.favicons.setAndFetchFaviconForPage(notificationURI, faviconURI,
+ true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ (faviconURI, iconSize, iconData, mimeType) => resolve(faviconURI),
+ Services.scriptSecurityManager.getSystemPrincipal());
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* dummyTabTask(aBrowser) {
+ yield openNotification(aBrowser, "showNotification2");
+
+ info("Notification alert showing");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ yield closeNotification(aBrowser);
+ return;
+ }
+
+ let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
+ is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
+ let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
+ is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present");
+ let alertIcon = alertWindow.document.getElementById("alertIcon");
+ is(alertIcon.src, faviconURI.spec, "Icon of notification should be present");
+
+ let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
+ is(alertCloseButton.localName, "toolbarbutton", "close button found");
+ let promiseBeforeUnloadEvent =
+ BrowserTestUtils.waitForEvent(alertWindow, "beforeunload");
+ let closedTime = alertWindow.Date.now();
+ alertCloseButton.click();
+ info("Clicked on close button");
+ yield promiseBeforeUnloadEvent;
+
+ ok(true, "Alert should close when the close button is clicked");
+ let currentTime = alertWindow.Date.now();
+ // The notification will self-close at 12 seconds, so this checks
+ // that the notification closed before the timeout.
+ ok(currentTime - closedTime < 5000,
+ "Close requested at " + closedTime + ", actually closed at " + currentTime);
+ });
+});
+
+add_task(function* cleanup() {
+ Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+ if (typeof oldShowFavicons == "boolean") {
+ Services.prefs.setBoolPref("alerts.showFavicons", oldShowFavicons);
+ }
+});
diff --git a/browser/base/content/test/alerts/browser_notification_do_not_disturb.js b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
new file mode 100644
index 000000000..92c689fd2
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
@@ -0,0 +1,80 @@
+"use strict";
+
+var tab;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+
+function test () {
+ waitForExplicitFinish();
+
+ try {
+ // Only run the test if the do-not-disturb
+ // interface has been implemented.
+ ALERT_SERVICE.manualDoNotDisturb;
+ ok(true, "Alert service implements do-not-disturb interface");
+ } catch (e) {
+ ok(true, "Alert service doesn't implement do-not-disturb interface, exiting test");
+ finish();
+ return;
+ }
+
+ let pm = Services.perms;
+ registerCleanupFunction(function() {
+ ALERT_SERVICE.manualDoNotDisturb = false;
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ gBrowser.removeTab(tab);
+ window.restore();
+ });
+
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ // Make sure that do-not-disturb is not enabled.
+ ok(!ALERT_SERVICE.manualDoNotDisturb, "Alert service should not be disabled when test starts");
+ ALERT_SERVICE.manualDoNotDisturb = false;
+
+ tab = gBrowser.addTab(notificationURL);
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", onLoad, true);
+}
+
+function onLoad() {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing);
+}
+
+function onAlertShowing() {
+ info("Notification alert showing");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ closeNotification(tab.linkedBrowser).then(finish);
+ return;
+ }
+ let doNotDisturbMenuItem = alertWindow.document.getElementById("doNotDisturbMenuItem");
+ is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found");
+ alertWindow.addEventListener("beforeunload", onAlertClosing);
+ doNotDisturbMenuItem.click();
+ info("Clicked on do-not-disturb menuitem");
+}
+
+function onAlertClosing(event) {
+ event.target.removeEventListener("beforeunload", onAlertClosing);
+
+ ok(ALERT_SERVICE.manualDoNotDisturb, "Alert service should be disabled after clicking menuitem");
+
+ // The notification should not appear, but there is
+ // no way from the client-side to know that it was
+ // blocked, except for waiting some time and realizing
+ // that the "onshow" event never fired.
+ openNotification(tab.linkedBrowser, "showNotification2", 2000)
+ .then(onAlert2Showing, finish);
+}
+
+function onAlert2Showing() {
+ ok(false, "the second alert should not have been shown");
+ closeNotification(tab.linkedBrowser).then(finish);
+}
diff --git a/browser/base/content/test/alerts/browser_notification_open_settings.js b/browser/base/content/test/alerts/browser_notification_open_settings.js
new file mode 100644
index 000000000..5306fd90a
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_open_settings.js
@@ -0,0 +1,58 @@
+"use strict";
+
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+add_task(function* test_settingsOpen_observer() {
+ info("Opening a dummy tab so openPreferences=>switchToTabHavingURI doesn't use the blank tab.");
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:robots"
+ }, function* dummyTabTask(aBrowser) {
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content");
+ info("simulate a notifications-open-settings notification");
+ let uri = NetUtil.newURI("https://example.com");
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.obs.notifyObservers(principal, "notifications-open-settings", null);
+ let tab = yield tabPromise;
+ ok(tab, "The notification settings tab opened");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* test_settingsOpen_button() {
+ let pm = Services.perms;
+ info("Adding notification permission");
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ try {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* tabTask(aBrowser) {
+ info("Waiting for notification");
+ yield openNotification(aBrowser, "showNotification2");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ yield closeNotification(aBrowser);
+ return;
+ }
+
+ let closePromise = promiseWindowClosed(alertWindow);
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content");
+ let openSettingsMenuItem = alertWindow.document.getElementById("openSettingsMenuItem");
+ openSettingsMenuItem.click();
+
+ info("Waiting for notification settings tab");
+ let tab = yield tabPromise;
+ ok(tab, "The notification settings tab opened");
+
+ yield closePromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+ } finally {
+ info("Removing notification permission");
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ }
+});
diff --git a/browser/base/content/test/alerts/browser_notification_permission_migration.js b/browser/base/content/test/alerts/browser_notification_permission_migration.js
new file mode 100644
index 000000000..b015e59a7
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_permission_migration.js
@@ -0,0 +1,45 @@
+const UI_VERSION = 32;
+
+var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
+ .getService(Ci.nsIObserver);
+var notificationURI = makeURI("http://example.org");
+var pm = Services.perms;
+var currentUIVersion;
+
+add_task(function* setup() {
+ currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
+ Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
+ pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION);
+});
+
+add_task(function* test_permissionMigration() {
+ if ("@mozilla.org/system-alerts-service;1" in Cc) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ return;
+ }
+
+ info("Waiting for migration notification");
+ let alertWindowPromise = promiseAlertWindow();
+ gBrowserGlue.observe(null, "browser-glue-test", "force-ui-migration");
+ let alertWindow = yield alertWindowPromise;
+
+ info("Clicking on notification");
+ let url =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "push#w_upgraded-notifications";
+ let closePromise = promiseWindowClosed(alertWindow);
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow);
+
+ info("Waiting for migration info tab");
+ let tab = yield tabPromise;
+ ok(tab, "The migration info tab opened");
+
+ yield closePromise;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* cleanup() {
+ Services.prefs.setIntPref("browser.migration.version", currentUIVersion);
+ pm.remove(notificationURI, "desktop-notification");
+});
diff --git a/browser/base/content/test/alerts/browser_notification_remove_permission.js b/browser/base/content/test/alerts/browser_notification_remove_permission.js
new file mode 100644
index 000000000..bd36faeae
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_remove_permission.js
@@ -0,0 +1,72 @@
+"use strict";
+
+var tab;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+var alertWindowClosed = false;
+var permRemoved = false;
+
+function test () {
+ waitForExplicitFinish();
+
+ let pm = Services.perms;
+ registerCleanupFunction(function() {
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ gBrowser.removeTab(tab);
+ window.restore();
+ });
+
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ tab = gBrowser.addTab(notificationURL);
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", onLoad, true);
+}
+
+function onLoad() {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing);
+}
+
+function onAlertShowing() {
+ info("Notification alert showing");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ closeNotification(tab.linkedBrowser).then(finish);
+ return;
+ }
+ ok(Services.perms.testExactPermission(makeURI(notificationURL), "desktop-notification"),
+ "Permission should exist prior to removal");
+ let disableForOriginMenuItem = alertWindow.document.getElementById("disableForOriginMenuItem");
+ is(disableForOriginMenuItem.localName, "menuitem", "menuitem found");
+ Services.obs.addObserver(permObserver, "perm-changed", false);
+ alertWindow.addEventListener("beforeunload", onAlertClosing);
+ disableForOriginMenuItem.click();
+ info("Clicked on disable-for-origin menuitem")
+}
+
+function permObserver(subject, topic, data) {
+ if (topic != "perm-changed") {
+ return;
+ }
+
+ let permission = subject.QueryInterface(Ci.nsIPermission);
+ is(permission.type, "desktop-notification", "desktop-notification permission changed");
+ is(data, "deleted", "desktop-notification permission deleted");
+
+ Services.obs.removeObserver(permObserver, "perm-changed");
+ permRemoved = true;
+ if (alertWindowClosed) {
+ finish();
+ }
+}
+
+function onAlertClosing(event) {
+ event.target.removeEventListener("beforeunload", onAlertClosing);
+
+ alertWindowClosed = true;
+ if (permRemoved) {
+ finish();
+ }
+}
diff --git a/browser/base/content/test/alerts/browser_notification_replace.js b/browser/base/content/test/alerts/browser_notification_replace.js
new file mode 100644
index 000000000..e678dc438
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_replace.js
@@ -0,0 +1,38 @@
+"use strict";
+
+let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+add_task(function* test_notificationReplace() {
+ let pm = Services.perms;
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* dummyTabTask(aBrowser) {
+ yield ContentTask.spawn(aBrowser, {}, function* () {
+ let win = content.window.wrappedJSObject;
+ let notification = win.showNotification1();
+ let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close");
+
+ let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show");
+ Assert.equal(showEvent.target.body, "Test body 1", "Showed tagged notification");
+
+ let newNotification = win.showNotification2();
+ let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show");
+ Assert.equal(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag");
+
+ let closeEvent = yield promiseCloseEvent;
+ Assert.equal(closeEvent.target.body, "Test body 1", "Closed previous tagged notification");
+
+ let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close");
+ newNotification.close();
+ let newCloseEvent = yield promiseNewCloseEvent;
+ Assert.equal(newCloseEvent.target.body, "Test body 2", "Closed new notification");
+ });
+ });
+});
+
+add_task(function* cleanup() {
+ Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+});
diff --git a/browser/base/content/test/alerts/browser_notification_tab_switching.js b/browser/base/content/test/alerts/browser_notification_tab_switching.js
new file mode 100644
index 000000000..7e46c0722
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_tab_switching.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+var tab;
+var notification;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+var newWindowOpenedFromTab;
+
+add_task(function* test_notificationPreventDefaultAndSwitchTabs() {
+ let pm = Services.perms;
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ let originalTab = gBrowser.selectedTab;
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* dummyTabTask(aBrowser) {
+ // Put new tab in background so it is obvious when it is re-focused.
+ yield BrowserTestUtils.switchTab(gBrowser, originalTab);
+ isnot(gBrowser.selectedBrowser, aBrowser, "Notification page loaded as a background tab");
+
+ // First, show a notification that will be have the tab-switching prevented.
+ function promiseNotificationEvent(evt) {
+ return ContentTask.spawn(aBrowser, evt, function* (evt) {
+ return yield new Promise(resolve => {
+ let notification = content.wrappedJSObject._notification;
+ notification.addEventListener(evt, function l(event) {
+ notification.removeEventListener(evt, l);
+ resolve({ defaultPrevented: event.defaultPrevented });
+ });
+ });
+ });
+ }
+ yield openNotification(aBrowser, "showNotification1");
+ info("Notification alert showing");
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ yield closeNotification(aBrowser);
+ return;
+ }
+ info("Clicking on notification");
+ let promiseClickEvent = promiseNotificationEvent("click");
+
+ // NB: This executeSoon is needed to allow the non-e10s runs of this test
+ // a chance to set the event listener on the page. Otherwise, we
+ // synchronously fire the click event before we listen for the event.
+ executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"),
+ {}, alertWindow);
+ });
+ let clickEvent = yield promiseClickEvent;
+ ok(clickEvent.defaultPrevented, "The event handler for the first notification cancels the event");
+ isnot(gBrowser.selectedBrowser, aBrowser, "Notification page still a background tab");
+ let notificationClosed = promiseNotificationEvent("close");
+ yield closeNotification(aBrowser);
+ yield notificationClosed;
+
+ // Second, show a notification that will cause the tab to get switched.
+ yield openNotification(aBrowser, "showNotification2");
+ alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ let promiseTabSelect = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabSelect");
+ EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"),
+ {},
+ alertWindow);
+ yield promiseTabSelect;
+ is(gBrowser.selectedBrowser.currentURI.spec, notificationURL,
+ "Clicking on the second notification should select its originating tab");
+ notificationClosed = promiseNotificationEvent("close");
+ yield closeNotification(aBrowser);
+ yield notificationClosed;
+ });
+});
+
+add_task(function* cleanup() {
+ Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+});
diff --git a/browser/base/content/test/alerts/file_dom_notifications.html b/browser/base/content/test/alerts/file_dom_notifications.html
new file mode 100644
index 000000000..6deede8fc
--- /dev/null
+++ b/browser/base/content/test/alerts/file_dom_notifications.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+"use strict";
+
+function showNotification1() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body 1",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ var n = new Notification("Test title", options);
+ n.addEventListener("click", function(event) {
+ event.preventDefault();
+ });
+ return n;
+}
+
+function showNotification2() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body 2",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ return new Notification("Test title", options);
+}
+</script>
+</head>
+<body>
+<form id="notificationForm" onsubmit="showNotification();">
+ <input type="submit" value="Show notification" id="submit"/>
+</form>
+</body>
+</html>
diff --git a/browser/base/content/test/alerts/head.js b/browser/base/content/test/alerts/head.js
new file mode 100644
index 000000000..21257de31
--- /dev/null
+++ b/browser/base/content/test/alerts/head.js
@@ -0,0 +1,71 @@
+function promiseAlertWindow() {
+ return new Promise(function(resolve) {
+ let listener = {
+ onOpenWindow(window) {
+ let alertWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ alertWindow.addEventListener("load", function onLoad() {
+ alertWindow.removeEventListener("load", onLoad);
+ let windowType = alertWindow.document.documentElement.getAttribute("windowtype");
+ if (windowType != "alert:alert") {
+ return;
+ }
+ Services.wm.removeListener(listener);
+ resolve(alertWindow);
+ });
+ },
+ };
+ Services.wm.addListener(listener);
+ });
+}
+
+/**
+ * Similar to `BrowserTestUtils.closeWindow`, but
+ * doesn't call `window.close()`.
+ */
+function promiseWindowClosed(window) {
+ return new Promise(function(resolve) {
+ Services.ww.registerNotification(function observer(subject, topic, data) {
+ if (topic == "domwindowclosed" && subject == window) {
+ Services.ww.unregisterNotification(observer);
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * These two functions work with file_dom_notifications.html to open the
+ * notification and close it.
+ *
+ * |fn| can be showNotification1 or showNotification2.
+ * if |timeout| is passed, then the promise returned from this function is
+ * rejected after the requested number of miliseconds.
+ */
+function openNotification(aBrowser, fn, timeout) {
+ return ContentTask.spawn(aBrowser, { fn, timeout }, function* ({ fn, timeout }) {
+ let win = content.wrappedJSObject;
+ let notification = win[fn]();
+ win._notification = notification;
+ yield new Promise((resolve, reject) => {
+ function listener() {
+ notification.removeEventListener("show", listener);
+ resolve();
+ }
+
+ notification.addEventListener("show", listener);
+
+ if (timeout) {
+ content.setTimeout(() => {
+ notification.removeEventListener("show", listener);
+ reject("timed out");
+ }, timeout);
+ }
+ });
+ });
+}
+
+function closeNotification(aBrowser) {
+ return ContentTask.spawn(aBrowser, null, function() {
+ content.wrappedJSObject._notification.close();
+ });
+}
diff --git a/browser/base/content/test/captivePortal/browser.ini b/browser/base/content/test/captivePortal/browser.ini
new file mode 100644
index 000000000..cfdbc5c2f
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_CaptivePortalWatcher.js]
+skip-if = os == "win" # Bug 1313894
+[browser_CaptivePortalWatcher_1.js]
+skip-if = os == "win" # Bug 1313894
+[browser_captivePortal_certErrorUI.js]
diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
new file mode 100644
index 000000000..e9c0fad6d
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
@@ -0,0 +1,119 @@
+"use strict";
+
+add_task(setupPrefsAndRecentWindowBehavior);
+
+// Each of the test cases below is run twice: once for login-success and once
+// for login-abort (aSuccess set to true and false respectively).
+let testCasesForBothSuccessAndAbort = [
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The portal tab should be added and focused when the window is
+ * opened, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_Open(aSuccess) {
+ yield portalDetected();
+ let win = yield focusWindowAndWaitForPortalUI();
+ yield freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when multiple browser windows are open but none
+ * have focus. A brower window is focused, then the portal is freed.
+ * The portal tab should be added and focused when the window is
+ * focused, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown in all windows upon
+ * detection, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_Focused(aSuccess) {
+ let win1 = yield openWindowAndWaitForFocus();
+ let win2 = yield openWindowAndWaitForFocus();
+ // Defocus both windows.
+ yield SimpleTest.promiseFocus(window);
+
+ yield portalDetected();
+
+ // Notification should be shown in both windows.
+ ensurePortalNotification(win1);
+ ensureNoPortalTab(win1);
+ ensurePortalNotification(win2);
+ ensureNoPortalTab(win2);
+
+ yield focusWindowAndWaitForPortalUI(false, win2);
+
+ yield freePortal(aSuccess);
+
+ ensureNoPortalNotification(win1);
+ ensureNoPortalTab(win2);
+ ensureNoPortalNotification(win2);
+
+ yield closeWindowAndWaitForXulWindowVisible(win2);
+ // No need to wait for xul-window-visible: after win2 is closed, focus
+ // is restored to the default window and win1 remains in the background.
+ yield BrowserTestUtils.closeWindow(win1);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The recheck triggered when the browser window is opened takes a
+ * long time. No portal tab should be added.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_LongRecheck(aSuccess) {
+ yield portalDetected();
+ let win = yield focusWindowAndWaitForPortalUI(true);
+ yield freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, and the
+ * portal is freed before a browser window is opened. No portal
+ * UI should be shown when a browser window is opened.
+ */
+ function* test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) {
+ yield portalDetected();
+ yield freePortal(aSuccess);
+ let win = yield openWindowAndWaitForFocus();
+ // Wait for a while to make sure no UI is shown.
+ yield new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when a browser window has focus. No portal tab should
+ * be opened. A notification bar should be displayed in all browser windows.
+ */
+ function* test_detectedWithFocus(aSuccess) {
+ let win1 = yield openWindowAndWaitForFocus();
+ let win2 = yield openWindowAndWaitForFocus();
+ yield portalDetected();
+ ensureNoPortalTab(win1);
+ ensureNoPortalTab(win2);
+ ensurePortalNotification(win1);
+ ensurePortalNotification(win2);
+ yield freePortal(aSuccess);
+ ensureNoPortalNotification(win1);
+ ensureNoPortalNotification(win2);
+ yield closeWindowAndWaitForXulWindowVisible(win2);
+ yield closeWindowAndWaitForXulWindowVisible(win1);
+ },
+];
+
+for (let testcase of testCasesForBothSuccessAndAbort) {
+ add_task(testcase.bind(null, true));
+ add_task(testcase.bind(null, false));
+}
diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js
new file mode 100644
index 000000000..71b12c32a
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js
@@ -0,0 +1,91 @@
+"use strict";
+
+add_task(setupPrefsAndRecentWindowBehavior);
+
+let testcases = [
+ /**
+ * A portal is detected when there's no browser window,
+ * then a browser window is opened, and the portal is logged into
+ * and redirects to a different page. The portal tab should be added
+ * and focused when the window is opened, and left open after login
+ * since it redirected.
+ */
+ function* test_detectedWithNoBrowserWindow_Redirect() {
+ yield portalDetected();
+ let win = yield focusWindowAndWaitForPortalUI();
+ let browser = win.gBrowser.selectedTab.linkedBrowser;
+ let loadPromise =
+ BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
+ BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
+ yield loadPromise;
+ yield freePortal(true);
+ ensurePortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * Test the various expected behaviors of the "Show Login Page" button
+ * in the captive portal notification. The button should be visible for
+ * all tabs except the captive portal tab, and when clicked, should
+ * ensure a captive portal tab is open and select it.
+ */
+ function* test_showLoginPageButton() {
+ let win = yield openWindowAndWaitForFocus();
+ yield portalDetected();
+ let notification = ensurePortalNotification(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+
+ function testPortalTabSelectedAndButtonNotVisible() {
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+ testShowLoginPageButtonVisibility(notification, "hidden");
+ }
+
+ let button = notification.querySelector("button.notification-button");
+ function* clickButtonAndExpectNewPortalTab() {
+ let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+ button.click();
+ let tab = yield p;
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+ return tab;
+ }
+
+ // Simulate clicking the button. The portal tab should be opened and
+ // selected and the button should hide.
+ let tab = yield clickButtonAndExpectNewPortalTab();
+ testPortalTabSelectedAndButtonNotVisible();
+
+ // Close the tab. The button should become visible.
+ yield BrowserTestUtils.removeTab(tab);
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+
+ // When the button is clicked, a new portal tab should be opened and
+ // selected.
+ tab = yield clickButtonAndExpectNewPortalTab();
+
+ // Open another arbitrary tab. The button should become visible. When it's clicked,
+ // the portal tab should be selected.
+ let anotherTab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ button.click();
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+
+ // Close the portal tab and select the arbitrary tab. The button should become
+ // visible and when it's clicked, a new portal tab should be opened.
+ yield BrowserTestUtils.removeTab(tab);
+ win.gBrowser.selectedTab = anotherTab;
+ testShowLoginPageButtonVisibility(notification, "visible");
+ tab = yield clickButtonAndExpectNewPortalTab();
+
+ yield BrowserTestUtils.removeTab(anotherTab);
+ yield freePortal(true);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+];
+
+for (let testcase of testcases) {
+ add_task(testcase);
+}
diff --git a/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
new file mode 100644
index 000000000..6b97e19a3
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BAD_CERT_PAGE = "https://expired.example.com/";
+
+// This tests the alternate cert error UI when we are behind a captive portal.
+
+add_task(function* checkCaptivePortalCertErrorUI() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT]],
+ });
+
+ let captivePortalStatePropagated = TestUtils.topicObserved("ipc:network:captive-portal-set-state");
+
+ info("Checking that the alternate about:certerror UI is shown when we are behind a captive portal.");
+ Services.obs.notifyObservers(null, "captive-portal-login", null);
+
+ info("Waiting for captive portal state to be propagated to the content process.");
+ yield captivePortalStatePropagated;
+
+ // Open a page with a cert error.
+ let browser;
+ let certErrorLoaded;
+ let errorTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ let tab = gBrowser.addTab(BAD_CERT_PAGE);
+ gBrowser.selectedTab = tab;
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ return tab;
+ }, false);
+
+ info("Waiting for cert error page to load.")
+ yield certErrorLoaded;
+
+ let portalTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, CANONICAL_URL);
+
+ yield ContentTask.spawn(browser, null, () => {
+ let doc = content.document;
+ ok(doc.body.classList.contains("captiveportal"),
+ "Captive portal error page UI is visible.");
+
+ info("Clicking the Open Login Page button.");
+ let loginButton = doc.getElementById("openPortalLoginPageButton");
+ is(loginButton.getAttribute("autofocus"), "true", "openPortalLoginPageButton has autofocus");
+ loginButton.click();
+ });
+
+ let portalTab = yield portalTabPromise;
+ is(gBrowser.selectedTab, portalTab, "Login page should be open in a new foreground tab.");
+
+ // Make sure clicking the "Open Login Page" button again focuses the existing portal tab.
+ yield BrowserTestUtils.switchTab(gBrowser, errorTab);
+ // Passing an empty function to BrowserTestUtils.switchTab lets us wait for an arbitrary
+ // tab switch.
+ portalTabPromise = BrowserTestUtils.switchTab(gBrowser, () => {});
+ yield ContentTask.spawn(browser, null, () => {
+ info("Clicking the Open Login Page button.");
+ content.document.getElementById("openPortalLoginPageButton").click();
+ });
+
+ let portalTab2 = yield portalTabPromise;
+ is(portalTab2, portalTab, "The existing portal tab should be focused.");
+
+ let portalTabRemoved = BrowserTestUtils.removeTab(portalTab, {dontRemove: true});
+ let errorTabReloaded = waitForCertErrorLoad(browser);
+
+ Services.obs.notifyObservers(null, "captive-portal-login-success", null);
+ yield portalTabRemoved;
+
+ info("Waiting for error tab to be reloaded after the captive portal was freed.");
+ yield errorTabReloaded;
+ yield ContentTask.spawn(browser, null, () => {
+ let doc = content.document;
+ ok(!doc.body.classList.contains("captiveportal"),
+ "Captive portal error page UI is not visible.");
+ });
+
+ yield BrowserTestUtils.removeTab(errorTab);
+});
diff --git a/browser/base/content/test/captivePortal/head.js b/browser/base/content/test/captivePortal/head.js
new file mode 100644
index 000000000..e40b5a325
--- /dev/null
+++ b/browser/base/content/test/captivePortal/head.js
@@ -0,0 +1,181 @@
+Components.utils.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CaptivePortalWatcher",
+ "resource:///modules/CaptivePortalWatcher.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cps",
+ "@mozilla.org/network/captive-portal-service;1",
+ "nsICaptivePortalService");
+
+const CANONICAL_CONTENT = "success";
+const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
+const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
+const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
+
+function* setupPrefsAndRecentWindowBehavior() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT]],
+ });
+ // We need to test behavior when a portal is detected when there is no browser
+ // window, but we can't close the default window opened by the test harness.
+ // Instead, we deactivate CaptivePortalWatcher in the default window and
+ // exclude it from RecentWindow.getMostRecentBrowserWindow in an attempt to
+ // mask its presence.
+ window.CaptivePortalWatcher.uninit();
+ let getMostRecentBrowserWindowCopy = RecentWindow.getMostRecentBrowserWindow;
+ let defaultWindow = window;
+ RecentWindow.getMostRecentBrowserWindow = () => {
+ let win = getMostRecentBrowserWindowCopy();
+ if (win == defaultWindow) {
+ return null;
+ }
+ return win;
+ };
+
+ registerCleanupFunction(function* cleanUp() {
+ RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindowCopy;
+ window.CaptivePortalWatcher.init();
+ });
+}
+
+function* portalDetected() {
+ Services.obs.notifyObservers(null, "captive-portal-login", null);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return cps.state == cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal detected.");
+}
+
+function* freePortal(aSuccess) {
+ Services.obs.notifyObservers(null,
+ "captive-portal-login-" + (aSuccess ? "success" : "abort"), null);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return cps.state != cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal freed.");
+}
+
+// If a window is provided, it will be focused. Otherwise, a new window
+// will be opened and focused.
+function* focusWindowAndWaitForPortalUI(aLongRecheck, win) {
+ // CaptivePortalWatcher triggers a recheck when a window gains focus. If
+ // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS,
+ // a tab with the login page is opened and selected. If it took longer,
+ // no tab is opened. It's not reliable to time things in an async test,
+ // so use a delay threshold of -1 to simulate a long recheck (so that any
+ // amount of time is considered excessive), and a very large threshold to
+ // simulate a short recheck.
+ Preferences.set("captivedetect.portalRecheckDelayMS", aLongRecheck ? -1 : 1000000);
+
+ if (!win) {
+ win = yield BrowserTestUtils.openNewBrowserWindow();
+ }
+ yield SimpleTest.promiseFocus(win);
+
+ // After a new window is opened, CaptivePortalWatcher asks for a recheck, and
+ // waits for it to complete. We need to manually tell it a recheck completed.
+ yield BrowserTestUtils.waitForCondition(() => {
+ return win.CaptivePortalWatcher._waitingForRecheck;
+ }, "Waiting for CaptivePortalWatcher to trigger a recheck.");
+ Services.obs.notifyObservers(null, "captive-portal-check-complete", null);
+
+ let notification = ensurePortalNotification(win);
+
+ if (aLongRecheck) {
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ return win;
+ }
+
+ let tab = win.gBrowser.tabs[1];
+ if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
+ // The tab should load the canonical URL, wait for it.
+ yield BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL);
+ }
+ is(win.gBrowser.selectedTab, tab,
+ "The captive portal tab should be open and selected in the new window.");
+ testShowLoginPageButtonVisibility(notification, "hidden");
+ return win;
+}
+
+function ensurePortalTab(win) {
+ // For the tests that call this function, it's enough to ensure there
+ // are two tabs in the window - the default tab and the portal tab.
+ is(win.gBrowser.tabs.length, 2,
+ "There should be a captive portal tab in the window.");
+}
+
+function ensurePortalNotification(win) {
+ let notificationBox =
+ win.document.getElementById("high-priority-global-notificationbox");
+ let notification = notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE)
+ isnot(notification, null,
+ "There should be a captive portal notification in the window.");
+ return notification;
+}
+
+// Helper to test whether the "Show Login Page" is visible in the captive portal
+// notification (it should be hidden when the portal tab is selected).
+function testShowLoginPageButtonVisibility(notification, visibility) {
+ let showLoginPageButton = notification.querySelector("button.notification-button");
+ // If the visibility property was never changed from default, it will be
+ // an empty string, so we pretend it's "visible" (effectively the same).
+ is(showLoginPageButton.style.visibility || "visible", visibility,
+ "The \"Show Login Page\" button should be " + visibility + ".");
+}
+
+function ensureNoPortalTab(win) {
+ is(win.gBrowser.tabs.length, 1,
+ "There should be no captive portal tab in the window.");
+}
+
+function ensureNoPortalNotification(win) {
+ let notificationBox =
+ win.document.getElementById("high-priority-global-notificationbox");
+ is(notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE), null,
+ "There should be no captive portal notification in the window.");
+}
+
+/**
+ * Some tests open a new window and close it later. When the window is closed,
+ * the original window opened by mochitest gains focus, generating a
+ * xul-window-visible notification. If the next test also opens a new window
+ * before this notification has a chance to fire, CaptivePortalWatcher picks
+ * up the first one instead of the one from the new window. To avoid this
+ * unfortunate intermittent timing issue, we wait for the notification from
+ * the original window every time we close a window that we opened.
+ */
+function waitForXulWindowVisible() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observe() {
+ Services.obs.removeObserver(observe, "xul-window-visible");
+ resolve();
+ }, "xul-window-visible", false);
+ });
+}
+
+function* closeWindowAndWaitForXulWindowVisible(win) {
+ let p = waitForXulWindowVisible();
+ yield BrowserTestUtils.closeWindow(win);
+ yield p;
+}
+
+/**
+ * BrowserTestUtils.openNewBrowserWindow() does not guarantee the newly
+ * opened window has received focus when the promise resolves, so we
+ * have to manually wait every time.
+ */
+function* openWindowAndWaitForFocus() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield SimpleTest.promiseFocus(win);
+ return win;
+}
+
+function waitForCertErrorLoad(browser) {
+ return new Promise(resolve => {
+ info("Waiting for DOMContentLoaded event");
+ browser.addEventListener("DOMContentLoaded", function load() {
+ browser.removeEventListener("DOMContentLoaded", load, false, true);
+ resolve();
+ }, false, true);
+ });
+}
diff --git a/browser/base/content/test/chrome/.eslintrc.js b/browser/base/content/test/chrome/.eslintrc.js
new file mode 100644
index 000000000..8c0f4f574
--- /dev/null
+++ b/browser/base/content/test/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/chrome/chrome.ini b/browser/base/content/test/chrome/chrome.ini
new file mode 100644
index 000000000..15035fc0c
--- /dev/null
+++ b/browser/base/content/test/chrome/chrome.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_aboutCrashed.xul]
diff --git a/browser/base/content/test/chrome/test_aboutCrashed.xul b/browser/base/content/test/chrome/test_aboutCrashed.xul
new file mode 100644
index 000000000..7a68076f1
--- /dev/null
+++ b/browser/base/content/test/chrome/test_aboutCrashed.xul
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <iframe type="content" id="frame1"/>
+ <iframe type="content" id="frame2" onload="doTest()"/>
+ <script type="application/javascript"><![CDATA[
+ const Ci = Components.interfaces;
+ const Cu = Components.utils;
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/Task.jsm");
+ Cu.import("resource://gre/modules/Promise.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Load error pages do not fire "load" events, so let's use a progressListener.
+ function waitForErrorPage(frame) {
+ let errorPageDeferred = Promise.defer();
+
+ let progressListener = {
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress)
+ .removeProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION);
+
+ errorPageDeferred.resolve();
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+ };
+
+ frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress)
+ .addProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION);
+
+ return errorPageDeferred.promise;
+ }
+
+ function doTest() {
+ Task.spawn(function test_aboutCrashed() {
+ let frame1 = document.getElementById("frame1");
+ let frame2 = document.getElementById("frame2");
+ let uri1 = Services.io.newURI("http://www.example.com/1", null, null);
+ let uri2 = Services.io.newURI("http://www.example.com/2", null, null);
+
+ let errorPageReady = waitForErrorPage(frame1);
+ frame1.docShell.chromeEventHandler.setAttribute("crashedPageTitle", "pageTitle");
+ frame1.docShell.displayLoadError(Components.results.NS_ERROR_CONTENT_CRASHED, uri1, null);
+
+ yield errorPageReady;
+ frame1.docShell.chromeEventHandler.removeAttribute("crashedPageTitle");
+
+ SimpleTest.is(frame1.contentDocument.documentURI,
+ "about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&f=regular&d=pageTitle",
+ "Correct about:tabcrashed displayed for page with title.");
+
+ errorPageReady = waitForErrorPage(frame2);
+ frame2.docShell.displayLoadError(Components.results.NS_ERROR_CONTENT_CRASHED, uri2, null);
+
+ yield errorPageReady;
+
+ SimpleTest.is(frame2.contentDocument.documentURI,
+ "about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&f=regular&d=%20",
+ "Correct about:tabcrashed displayed for page with no title.");
+
+ SimpleTest.finish();
+ });
+ }
+ ]]></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" />
+</window>
diff --git a/browser/base/content/test/general/.eslintrc.js b/browser/base/content/test/general/.eslintrc.js
new file mode 100644
index 000000000..11abd6140
--- /dev/null
+++ b/browser/base/content/test/general/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js",
+ "../../../../../testing/mochitest/mochitest.eslintrc.js",
+ ]
+};
diff --git a/browser/base/content/test/general/POSTSearchEngine.xml b/browser/base/content/test/general/POSTSearchEngine.xml
new file mode 100644
index 000000000..30567d92f
--- /dev/null
+++ b/browser/base/content/test/general/POSTSearchEngine.xml
@@ -0,0 +1,6 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>POST Search</ShortName>
+ <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/base/content/test/general/print_postdata.sjs">
+ <Param name="searchterms" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/browser/base/content/test/general/aboutHome_content_script.js b/browser/base/content/test/general/aboutHome_content_script.js
new file mode 100644
index 000000000..28d0e617e
--- /dev/null
+++ b/browser/base/content/test/general/aboutHome_content_script.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+addMessageListener("AboutHome:SearchTriggered", function (msg) {
+ sendAsyncMessage("AboutHomeTest:CheckRecordedSearch", msg.data);
+});
diff --git a/browser/base/content/test/general/accounts_testRemoteCommands.html b/browser/base/content/test/general/accounts_testRemoteCommands.html
new file mode 100644
index 000000000..517317aff
--- /dev/null
+++ b/browser/base/content/test/general/accounts_testRemoteCommands.html
@@ -0,0 +1,83 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+
+<script type="text/javascript;version=1.8">
+
+function init() {
+ window.addEventListener("message", function process(e) {doTest(e)}, false);
+ // unless we relinquish the eventloop,
+ // tests will run before the chrome event handlers are ready
+ setTimeout(doTest, 0);
+}
+
+function checkStatusValue(payload, expectedValue) {
+ return payload.status == expectedValue;
+}
+
+let tests = [
+{
+ info: "Check account log in",
+ event: "login",
+ data: {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: true
+ },
+ payloadType: "message",
+ validateResponse: function(payload) {
+ return checkStatusValue(payload, "login");
+ },
+},
+];
+
+let currentTest = -1;
+function doTest(evt) {
+ if (evt) {
+ if (currentTest < 0 || !evt.data.content)
+ return; // not yet testing
+
+ let test = tests[currentTest];
+ if (evt.data.type != test.payloadType)
+ return; // skip unrequested events
+
+ let error = JSON.stringify(evt.data.content);
+ let pass = false;
+ try {
+ pass = test.validateResponse(evt.data.content)
+ } catch (e) {}
+ reportResult(test.info, pass, error);
+ }
+ // start the next test if there are any left
+ if (tests[++currentTest])
+ sendToBrowser(tests[currentTest].event, tests[currentTest].data);
+ else
+ reportFinished();
+}
+
+function reportResult(info, pass, error) {
+ let data = {type: "testResult", info: info, pass: pass, error: error};
+ let event = new CustomEvent("FirefoxAccountsTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function reportFinished(cmd) {
+ let data = {type: "testsComplete", count: tests.length};
+ let event = new CustomEvent("FirefoxAccountsTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function sendToBrowser(type, data) {
+ let event = new CustomEvent("FirefoxAccountsCommand", {detail: {command: type, data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+</script>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/browser/base/content/test/general/alltabslistener.html b/browser/base/content/test/general/alltabslistener.html
new file mode 100644
index 000000000..166c31037
--- /dev/null
+++ b/browser/base/content/test/general/alltabslistener.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Test page for bug 463387</title>
+</head>
+<body>
+<p>Test page for bug 463387</p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/app_bug575561.html b/browser/base/content/test/general/app_bug575561.html
new file mode 100644
index 000000000..a60c7c87e
--- /dev/null
+++ b/browser/base/content/test/general/app_bug575561.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tabs</title>
+ </head>
+ <body>
+ <a href="http://example.com/browser/browser/base/content/test/general/dummy_page.html">same domain</a>
+ <a href="http://test1.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (different subdomain)</a>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html">different domain</a>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html" target="foo">different domain (with target)</a>
+ <a href="http://www.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (www prefix)</a>
+ <a href="data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>">data: URI</a>
+ <iframe src="app_subframe_bug575561.html"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/app_subframe_bug575561.html b/browser/base/content/test/general/app_subframe_bug575561.html
new file mode 100644
index 000000000..8690497ff
--- /dev/null
+++ b/browser/base/content/test/general/app_subframe_bug575561.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tab subframes</title>
+ </head>
+ <body>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html">different domain</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/audio.ogg b/browser/base/content/test/general/audio.ogg
new file mode 100644
index 000000000..477544875
--- /dev/null
+++ b/browser/base/content/test/general/audio.ogg
Binary files differ
diff --git a/browser/base/content/test/general/benignPage.html b/browser/base/content/test/general/benignPage.html
new file mode 100644
index 000000000..8e9429acd
--- /dev/null
+++ b/browser/base/content/test/general/benignPage.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <iframe src="http://not-tracking.example.com/"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini
new file mode 100644
index 000000000..96e591ffe
--- /dev/null
+++ b/browser/base/content/test/general/browser.ini
@@ -0,0 +1,494 @@
+[DEFAULT]
+support-files =
+ POSTSearchEngine.xml
+ accounts_testRemoteCommands.html
+ alltabslistener.html
+ app_bug575561.html
+ app_subframe_bug575561.html
+ aboutHome_content_script.js
+ audio.ogg
+ browser_bug479408_sample.html
+ browser_bug678392-1.html
+ browser_bug678392-2.html
+ browser_bug970746.xhtml
+ browser_fxa_oauth.html
+ browser_fxa_oauth_with_keys.html
+ browser_fxa_web_channel.html
+ browser_registerProtocolHandler_notification.html
+ browser_star_hsts.sjs
+ browser_tab_dragdrop2_frame1.xul
+ browser_web_channel.html
+ browser_web_channel_iframe.html
+ bug1262648_string_with_newlines.dtd
+ bug592338.html
+ bug792517-2.html
+ bug792517.html
+ bug792517.sjs
+ bug839103.css
+ clipboard_pastefile.html
+ contextmenu_common.js
+ ctxmenu-image.png
+ discovery.html
+ download_page.html
+ dummy_page.html
+ feed_tab.html
+ file_generic_favicon.ico
+ file_with_favicon.html
+ file_bug822367_1.html
+ file_bug822367_1.js
+ file_bug822367_2.html
+ file_bug822367_3.html
+ file_bug822367_4.html
+ file_bug822367_4.js
+ file_bug822367_4B.html
+ file_bug822367_5.html
+ file_bug822367_6.html
+ file_bug902156.js
+ file_bug902156_1.html
+ file_bug902156_2.html
+ file_bug902156_3.html
+ file_bug906190_1.html
+ file_bug906190_2.html
+ file_bug906190_3_4.html
+ file_bug906190_redirected.html
+ file_bug906190.js
+ file_bug906190.sjs
+ file_mediaPlayback.html
+ file_mixedContentFromOnunload.html
+ file_mixedContentFromOnunload_test1.html
+ file_mixedContentFromOnunload_test2.html
+ file_mixedContentFramesOnHttp.html
+ file_mixedPassiveContent.html
+ file_bug970276_popup1.html
+ file_bug970276_popup2.html
+ file_bug970276_favicon1.ico
+ file_bug970276_favicon2.ico
+ file_documentnavigation_frameset.html
+ file_double_close_tab.html
+ file_favicon_change.html
+ file_favicon_change_not_in_document.html
+ file_fullscreen-window-open.html
+ head.js
+ healthreport_pingData.js
+ healthreport_testRemoteCommands.html
+ moz.png
+ navigating_window_with_download.html
+ offlineQuotaNotification.cacheManifest
+ offlineQuotaNotification.html
+ page_style_sample.html
+ parsingTestHelpers.jsm
+ pinning_headers.sjs
+ ssl_error_reports.sjs
+ print_postdata.sjs
+ searchSuggestionEngine.sjs
+ searchSuggestionEngine.xml
+ searchSuggestionEngine2.xml
+ subtst_contextmenu.html
+ subtst_contextmenu_input.html
+ subtst_contextmenu_xul.xul
+ test-mixedcontent-securityerrors.html
+ test_bug435035.html
+ test_bug462673.html
+ test_bug628179.html
+ test_bug839103.html
+ test_bug959531.html
+ test_process_flags_chrome.html
+ title_test.svg
+ unknownContentType_file.pif
+ unknownContentType_file.pif^headers^
+ video.ogg
+ web_video.html
+ web_video1.ogv
+ web_video1.ogv^headers^
+ zoom_test.html
+ test_no_mcb_on_http_site_img.html
+ test_no_mcb_on_http_site_img.css
+ test_no_mcb_on_http_site_font.html
+ test_no_mcb_on_http_site_font.css
+ test_no_mcb_on_http_site_font2.html
+ test_no_mcb_on_http_site_font2.css
+ test_mcb_redirect.html
+ test_mcb_redirect_image.html
+ test_mcb_double_redirect_image.html
+ test_mcb_redirect.js
+ test_mcb_redirect.sjs
+ file_bug1045809_1.html
+ file_bug1045809_2.html
+ file_csp_block_all_mixedcontent.html
+ file_csp_block_all_mixedcontent.js
+ !/image/test/mochitest/blue.png
+ !/toolkit/components/passwordmgr/test/browser/form_basic.html
+ !/toolkit/components/passwordmgr/test/browser/insecure_test.html
+ !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
+ !/toolkit/content/tests/browser/common/mockTransfer.js
+ !/toolkit/modules/tests/browser/metadata_*.html
+ !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html
+ !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
+ !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
+
+[browser_aboutAccounts.js]
+skip-if = os == "linux" # Bug 958026
+support-files =
+ content_aboutAccounts.js
+[browser_aboutCertError.js]
+[browser_aboutNetError.js]
+[browser_aboutSupport_newtab_security_state.js]
+[browser_aboutHealthReport.js]
+skip-if = os == "linux" # Bug 924307
+[browser_aboutHome.js]
+[browser_aboutHome_wrapsCorrectly.js]
+[browser_addKeywordSearch.js]
+[browser_alltabslistener.js]
+[browser_audioTabIcon.js]
+tags = audiochannel
+[browser_backButtonFitts.js]
+skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
+[browser_beforeunload_duplicate_dialogs.js]
+[browser_blob-channelname.js]
+[browser_bookmark_popup.js]
+skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds
+[browser_bookmark_titles.js]
+skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
+[browser_bug321000.js]
+subsuite = clipboard
+skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
+[browser_bug356571.js]
+[browser_bug380960.js]
+[browser_bug386835.js]
+[browser_bug406216.js]
+[browser_bug408415.js]
+[browser_bug409481.js]
+[browser_bug409624.js]
+[browser_bug413915.js]
+[browser_bug416661.js]
+[browser_bug417483.js]
+[browser_bug419612.js]
+[browser_bug422590.js]
+[browser_bug423833.js]
+skip-if = true # bug 428712
+[browser_bug424101.js]
+[browser_bug427559.js]
+[browser_bug431826.js]
+[browser_bug432599.js]
+[browser_bug435035.js]
+[browser_bug435325.js]
+[browser_bug441778.js]
+[browser_bug455852.js]
+[browser_bug460146.js]
+[browser_bug462289.js]
+skip-if = toolkit == "cocoa"
+[browser_bug462673.js]
+[browser_bug477014.js]
+[browser_bug479408.js]
+[browser_bug481560.js]
+[browser_bug484315.js]
+[browser_bug491431.js]
+[browser_bug495058.js]
+[browser_bug517902.js]
+skip-if = (os == 'linux' && e10s) # bug 1161699
+[browser_bug519216.js]
+[browser_bug520538.js]
+[browser_bug521216.js]
+[browser_bug533232.js]
+[browser_bug537013.js]
+subsuite = clipboard
+skip-if = e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
+[browser_bug537474.js]
+[browser_bug550565.js]
+[browser_bug553455.js]
+[browser_bug555224.js]
+[browser_bug555767.js]
+[browser_bug559991.js]
+[browser_bug561636.js]
+skip-if = true # bug 1057615
+[browser_bug563588.js]
+[browser_bug565575.js]
+[browser_bug567306.js]
+subsuite = clipboard
+[browser_bug1261299.js]
+subsuite = clipboard
+skip-if = toolkit != "cocoa" # Because of tests for supporting Service Menu of macOS, bug 1261299
+[browser_bug1297539.js]
+skip-if = toolkit != "cocoa" # Because of tests for supporting pasting from Service Menu of macOS, bug 1297539
+[browser_bug575561.js]
+[browser_bug575830.js]
+[browser_bug577121.js]
+[browser_bug578534.js]
+[browser_bug579872.js]
+[browser_bug580638.js]
+[browser_bug580956.js]
+[browser_bug581242.js]
+[browser_bug581253.js]
+[browser_bug585558.js]
+[browser_bug585785.js]
+[browser_bug585830.js]
+[browser_bug590206.js]
+[browser_bug592338.js]
+[browser_bug594131.js]
+[browser_bug595507.js]
+skip-if = true # bug 1057615
+[browser_bug596687.js]
+[browser_bug597218.js]
+[browser_bug609700.js]
+[browser_bug623893.js]
+[browser_bug624734.js]
+[browser_bug633691.js]
+[browser_bug647886.js]
+[browser_bug655584.js]
+[browser_bug664672.js]
+[browser_bug676619.js]
+skip-if = os == "mac" # mac: Intermittent failures, bug 925225
+[browser_bug678392.js]
+skip-if = os == "mac" # Bug 1102331 - does focus things on the content window which break in e10s mode (still causes orange on Mac 10.10)
+[browser_bug710878.js]
+[browser_bug719271.js]
+[browser_bug724239.js]
+[browser_bug734076.js]
+[browser_bug735471.js]
+[browser_bug749738.js]
+[browser_bug763468_perwindowpb.js]
+[browser_bug767836_perwindowpb.js]
+[browser_bug817947.js]
+[browser_bug822367.js]
+tags = mcb
+[browser_bug832435.js]
+[browser_bug839103.js]
+[browser_bug882977.js]
+[browser_bug902156.js]
+tags = mcb
+[browser_bug906190.js]
+tags = mcb
+[browser_mixedContentFromOnunload.js]
+tags = mcb
+[browser_mixedContentFramesOnHttp.js]
+tags = mcb
+[browser_bug970746.js]
+[browser_bug1015721.js]
+skip-if = os == 'win'
+[browser_bug1064280_changeUrlInPinnedTab.js]
+[browser_accesskeys.js]
+[browser_clipboard.js]
+subsuite = clipboard
+[browser_clipboard_pastefile.js]
+skip-if = true # Disabled due to the clipboard not supporting real file types yet (bug 1288773)
+[browser_contentAreaClick.js]
+skip-if = e10s # Clicks in content don't go through contentAreaClick with e10s.
+[browser_contentAltClick.js]
+[browser_contextmenu.js]
+subsuite = clipboard
+tags = fullscreen
+skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+[browser_contextmenu_input.js]
+skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+[browser_ctrlTab.js]
+[browser_datachoices_notification.js]
+skip-if = !datareporting
+[browser_decoderDoctor.js]
+skip-if = os == "mac" # decoder doctor isn't implemented on osx
+[browser_devedition.js]
+[browser_discovery.js]
+[browser_double_close_tab.js]
+[browser_documentnavigation.js]
+[browser_duplicateIDs.js]
+[browser_drag.js]
+skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
+[browser_favicon_change.js]
+[browser_favicon_change_not_in_document.js]
+[browser_findbarClose.js]
+[browser_focusonkeydown.js]
+[browser_fullscreen-window-open.js]
+tags = fullscreen
+skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
+[browser_fxaccounts.js]
+support-files = fxa_profile_handler.sjs
+[browser_fxa_migrate.js]
+[browser_fxa_oauth.js]
+[browser_fxa_web_channel.js]
+[browser_gestureSupport.js]
+skip-if = e10s # Bug 863514 - no gesture support.
+[browser_getshortcutoruri.js]
+[browser_hide_removing.js]
+[browser_homeDrop.js]
+[browser_identity_UI.js]
+[browser_insecureLoginForms.js]
+support-files = insecure_opener.html
+[browser_invalid_uri_back_forward_manipulation.js]
+[browser_keywordBookmarklets.js]
+[browser_keywordSearch.js]
+[browser_keywordSearch_postData.js]
+[browser_lastAccessedTab.js]
+skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
+[browser_menuButtonFitts.js]
+skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
+[browser_middleMouse_noJSPaste.js]
+subsuite = clipboard
+[browser_minimize.js]
+[browser_misused_characters_in_strings.js]
+[browser_mixed_content_cert_override.js]
+[browser_mixedcontent_securityflags.js]
+tags = mcb
+[browser_modifiedclick_inherit_principal.js]
+[browser_offlineQuotaNotification.js]
+skip-if = os == "linux" && !debug # bug 1304273
+[browser_feed_discovery.js]
+support-files = feed_discovery.html
+[browser_gZipOfflineChild.js]
+support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
+[browser_overflowScroll.js]
+[browser_pageInfo.js]
+[browser_pageinfo_svg_image.js]
+support-files =
+ svg_image.html
+[browser_page_style_menu.js]
+[browser_page_style_menu_update.js]
+[browser_parsable_css.js]
+skip-if = (debug || asan) # no point in running on both opt and debug, and will likely intermittently timeout on debug
+[browser_parsable_script.js]
+skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue.
+[browser_permissions.js]
+support-files =
+ permissions.html
+[browser_pinnedTabs.js]
+[browser_plainTextLinks.js]
+[browser_printpreview.js]
+[browser_private_browsing_window.js]
+[browser_private_no_prompt.js]
+[browser_purgehistory_clears_sh.js]
+[browser_PageMetaData_pushstate.js]
+[browser_refreshBlocker.js]
+support-files =
+ refresh_header.sjs
+ refresh_meta.sjs
+[browser_relatedTabs.js]
+[browser_remoteTroubleshoot.js]
+support-files =
+ test_remoteTroubleshoot.html
+[browser_remoteWebNavigation_postdata.js]
+[browser_removeTabsToTheEnd.js]
+[browser_restore_isAppTab.js]
+[browser_sanitize-passwordDisabledHosts.js]
+[browser_sanitize-sitepermissions.js]
+[browser_sanitize-timespans.js]
+[browser_sanitizeDialog.js]
+[browser_save_link-perwindowpb.js]
+skip-if = e10s && debug && os == "win" # Bug 1280505
+[browser_save_private_link_perwindowpb.js]
+[browser_save_link_when_window_navigates.js]
+[browser_save_video.js]
+[browser_save_video_frame.js]
+[browser_scope.js]
+[browser_contentSearchUI.js]
+support-files =
+ contentSearchUI.html
+ contentSearchUI.js
+[browser_selectpopup.js]
+run-if = e10s
+[browser_selectTabAtIndex.js]
+[browser_ssl_error_reports.js]
+[browser_star_hsts.js]
+[browser_subframe_favicons_not_used.js]
+[browser_syncui.js]
+[browser_tab_close_dependent_window.js]
+[browser_tabDrop.js]
+[browser_tabReorder.js]
+[browser_tab_detach_restore.js]
+[browser_tab_drag_drop_perwindow.js]
+[browser_tab_dragdrop.js]
+skip-if = buildapp == 'mulet' || (e10s && (debug || os == 'linux')) # Bug 1312436
+[browser_tab_dragdrop2.js]
+[browser_tabbar_big_widgets.js]
+skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
+ # Disabled on OS X because of bug 967917
+[browser_tabfocus.js]
+[browser_tabkeynavigation.js]
+skip-if = (os == "mac" && !e10s) # Bug 1237713 - OSX eats keypresses for some reason
+[browser_tabopen_reflows.js]
+[browser_tabs_close_beforeunload.js]
+support-files =
+ close_beforeunload_opens_second_tab.html
+ close_beforeunload.html
+[browser_tabs_isActive.js]
+[browser_tabs_owner.js]
+[browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
+run-if = e10s
+[browser_trackingUI_1.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+ benignPage.html
+[browser_trackingUI_2.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+ benignPage.html
+[browser_trackingUI_3.js]
+tags = trackingprotection
+[browser_trackingUI_4.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+ benignPage.html
+[browser_trackingUI_5.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+[browser_trackingUI_6.js]
+tags = trackingprotection
+support-files =
+ file_trackingUI_6.html
+ file_trackingUI_6.js
+ file_trackingUI_6.js^headers^
+[browser_trackingUI_telemetry.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+[browser_typeAheadFind.js]
+[browser_unknownContentType_title.js]
+[browser_unloaddialogs.js]
+[browser_utilityOverlay.js]
+[browser_viewSourceInTabOnViewSource.js]
+[browser_visibleFindSelection.js]
+[browser_visibleTabs.js]
+[browser_visibleTabs_bookmarkAllPages.js]
+skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
+[browser_visibleTabs_bookmarkAllTabs.js]
+[browser_visibleTabs_contextMenu.js]
+[browser_visibleTabs_tabPreview.js]
+skip-if = (os == "win" && !debug)
+[browser_web_channel.js]
+[browser_windowopen_reflows.js]
+[browser_zbug569342.js]
+skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
+[browser_registerProtocolHandler_notification.js]
+[browser_no_mcb_on_http_site.js]
+tags = mcb
+[browser_addCertException.js]
+[browser_bug1045809.js]
+tags = mcb
+[browser_e10s_switchbrowser.js]
+[browser_e10s_about_process.js]
+[browser_e10s_chrome_process.js]
+[browser_e10s_javascript.js]
+[browser_blockHPKP.js]
+tags = psm
+[browser_mcb_redirect.js]
+tags = mcb
+[browser_windowactivation.js]
+[browser_contextmenu_childprocess.js]
+[browser_bug963945.js]
+[browser_domFullscreen_fullscreenMode.js]
+tags = fullscreen
+[browser_menuButtonBadgeManager.js]
+[browser_newTabDrop.js]
+[browser_newWindowDrop.js]
+[browser_csp_block_all_mixedcontent.js]
+tags = mcb
+[browser_newwindow_focus.js]
+skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason.
+[browser_bug1299667.js]
diff --git a/browser/base/content/test/general/browser_PageMetaData_pushstate.js b/browser/base/content/test/general/browser_PageMetaData_pushstate.js
new file mode 100644
index 000000000..6f71c57a3
--- /dev/null
+++ b/browser/base/content/test/general/browser_PageMetaData_pushstate.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* () {
+ let rooturi = "https://example.com/browser/toolkit/modules/tests/browser/";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, rooturi + "metadata_simple.html");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { rooturi }, function* (args) {
+ let result = PageMetadata.getData(content.document);
+ // Result should have description.
+ Assert.equal(result.url, args.rooturi + "metadata_simple.html", "metadata url is correct");
+ Assert.equal(result.title, "Test Title", "metadata title is correct");
+ Assert.equal(result.description, "A very simple test page", "description is correct");
+
+ content.history.pushState({}, "2", "2.html");
+ result = PageMetadata.getData(content.document);
+ // Result should not have description.
+ Assert.equal(result.url, args.rooturi + "2.html", "metadata url is correct");
+ Assert.equal(result.title, "Test Title", "metadata title is correct");
+ Assert.ok(!result.description, "description is undefined");
+
+ Assert.equal(content.document.documentURI, args.rooturi + "2.html",
+ "content.document has correct url");
+ });
+
+ is(gBrowser.currentURI.spec, rooturi + "2.html", "gBrowser has correct url");
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_aboutAccounts.js b/browser/base/content/test/general/browser_aboutAccounts.js
new file mode 100644
index 000000000..fd72a1608
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutAccounts.js
@@ -0,0 +1,499 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: window.location is null");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+// Preference helpers.
+var changedPrefs = new Set();
+
+function setPref(name, value) {
+ changedPrefs.add(name);
+ Services.prefs.setCharPref(name, value);
+}
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ for (let name of changedPrefs) {
+ Services.prefs.clearUserPref(name);
+ }
+});
+
+var gTests = [
+{
+ desc: "Test the remote commands",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ setPref("identity.fxaccounts.remote.signup.uri",
+ "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
+ let tab = yield promiseNewTabLoadEvent("about:accounts");
+ let mm = tab.linkedBrowser.messageManager;
+
+ let deferred = Promise.defer();
+
+ // We'll get a message when openPrefs() is called, which this test should
+ // arrange.
+ let promisePrefsOpened = promiseOneMessage(tab, "test:openPrefsCalled");
+ let results = 0;
+ try {
+ mm.addMessageListener("test:response", function responseHandler(msg) {
+ let data = msg.data.data;
+ if (data.type == "testResult") {
+ ok(data.pass, data.info);
+ results++;
+ } else if (data.type == "testsComplete") {
+ is(results, data.count, "Checking number of results received matches the number of tests that should have run");
+ mm.removeMessageListener("test:response", responseHandler);
+ deferred.resolve();
+ }
+ });
+ } catch (e) {
+ ok(false, "Failed to get all commands");
+ deferred.reject();
+ }
+ yield deferred.promise;
+ yield promisePrefsOpened;
+ }
+},
+{
+ desc: "Test action=signin - no user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ const expected_url = "https://example.com/?is_sign_in";
+ setPref("identity.fxaccounts.remote.signin.uri", expected_url);
+ let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin");
+ is(url, expected_url, "action=signin got the expected URL");
+ // we expect the remote iframe to be shown.
+ yield checkVisibilities(tab, {
+ stage: false, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: true,
+ networkError: false
+ });
+ }
+},
+{
+ desc: "Test action=signin - user logged in",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ // When this loads with a user logged-in, we expect the normal URL to
+ // have been ignored and the "manage" page to be shown.
+ const expected_url = "https://example.com/?is_sign_in";
+ setPref("identity.fxaccounts.remote.signin.uri", expected_url);
+ yield setSignedInUser();
+ let tab = yield promiseNewTabLoadEvent("about:accounts?action=signin");
+ // about:accounts initializes after fetching the current user from Fxa -
+ // so we also request it - by the time we get it we know it should have
+ // done its thing.
+ yield fxAccounts.getSignedInUser();
+ // we expect "manage" to be shown.
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: true,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: false
+ });
+ }
+},
+{
+ desc: "Test action=signin - captive portal",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ const signinUrl = "https://redirproxy.example.com/test";
+ setPref("identity.fxaccounts.remote.signin.uri", signinUrl);
+ let [tab, ] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin");
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: true
+ });
+ }
+},
+{
+ desc: "Test action=signin - offline",
+ teardown: () => {
+ gBrowser.removeCurrentTab();
+ BrowserOffline.toggleOfflineStatus();
+ },
+ run: function* ()
+ {
+ BrowserOffline.toggleOfflineStatus();
+ Services.cache2.clear();
+
+ const signinUrl = "https://unknowndomain.cow";
+ setPref("identity.fxaccounts.remote.signin.uri", signinUrl);
+ let [tab, ] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin");
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: true
+ });
+ }
+},
+{
+ desc: "Test action=signup - no user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ const expected_url = "https://example.com/?is_sign_up";
+ setPref("identity.fxaccounts.remote.signup.uri", expected_url);
+ let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signup");
+ is(url, expected_url, "action=signup got the expected URL");
+ // we expect the remote iframe to be shown.
+ yield checkVisibilities(tab, {
+ stage: false, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: true,
+ networkError: false
+ });
+ },
+},
+{
+ desc: "Test action=signup - user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ const expected_url = "https://example.com/?is_sign_up";
+ setPref("identity.fxaccounts.remote.signup.uri", expected_url);
+ yield setSignedInUser();
+ let tab = yield promiseNewTabLoadEvent("about:accounts?action=signup");
+ yield fxAccounts.getSignedInUser();
+ // we expect "manage" to be shown.
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: true,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: false
+ });
+ },
+},
+{
+ desc: "Test action=reauth",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ const expected_url = "https://example.com/?is_force_auth";
+ setPref("identity.fxaccounts.remote.force_auth.uri", expected_url);
+
+ yield setSignedInUser();
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=reauth");
+ // The current user will be appended to the url
+ let expected = expected_url + "&email=foo%40example.com";
+ is(url, expected, "action=reauth got the expected URL");
+ },
+},
+{
+ desc: "Test with migrateToDevEdition enabled (success)",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ let fxAccountsCommon = {};
+ Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
+ const pref = "identity.fxaccounts.migrateToDevEdition";
+ changedPrefs.add(pref);
+ Services.prefs.setBoolPref(pref, true);
+
+ // Create the signedInUser.json file that will be used as the source of
+ // migrated user data.
+ let signedInUser = {
+ version: 1,
+ accountData: {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ sessionToken: "dead",
+ verified: true
+ }
+ };
+ // We use a sub-dir of the real profile dir as the "pretend" profile dir
+ // for this test.
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let mockDir = profD.clone();
+ mockDir.append("about-accounts-mock-profd");
+ mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ let fxAccountsStorage = OS.Path.join(mockDir.path, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
+ yield OS.File.writeAtomic(fxAccountsStorage, JSON.stringify(signedInUser));
+ info("Wrote file " + fxAccountsStorage);
+
+ // this is a little subtle - we load about:robots so we get a non-remote
+ // tab, then we send a message which does both (a) load the URL we want and
+ // (b) mocks the default profile path used by about:accounts.
+ let tab = yield promiseNewTabLoadEvent("about:robots");
+ let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response");
+
+ let mm = tab.linkedBrowser.messageManager;
+ mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
+ url: "about:accounts",
+ profilePath: mockDir.path,
+ });
+
+ let response = yield readyPromise;
+ // We are expecting the iframe to be on the "force reauth" URL
+ let expected = yield fxAccounts.promiseAccountsForceSigninURI();
+ is(response.data.url, expected);
+
+ let userData = yield fxAccounts.getSignedInUser();
+ SimpleTest.isDeeply(userData, signedInUser.accountData, "All account data were migrated");
+ // The migration pref will have been switched off by now.
+ is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
+
+ yield OS.File.remove(fxAccountsStorage);
+ yield OS.File.removeEmptyDir(mockDir.path);
+ },
+},
+{
+ desc: "Test with migrateToDevEdition enabled (no user to migrate)",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ const pref = "identity.fxaccounts.migrateToDevEdition";
+ changedPrefs.add(pref);
+ Services.prefs.setBoolPref(pref, true);
+
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let mockDir = profD.clone();
+ mockDir.append("about-accounts-mock-profd");
+ mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ // but leave it empty, so we don't think a user is logged in.
+
+ let tab = yield promiseNewTabLoadEvent("about:robots");
+ let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response");
+
+ let mm = tab.linkedBrowser.messageManager;
+ mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
+ url: "about:accounts",
+ profilePath: mockDir.path,
+ });
+
+ let response = yield readyPromise;
+ // We are expecting the iframe to be on the "signup" URL
+ let expected = yield fxAccounts.promiseAccountsSignUpURI();
+ is(response.data.url, expected);
+
+ // and expect no signed in user.
+ let userData = yield fxAccounts.getSignedInUser();
+ is(userData, null);
+ // The migration pref should have still been switched off.
+ is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
+ yield OS.File.removeEmptyDir(mockDir.path);
+ },
+},
+{
+ desc: "Test observers about:accounts",
+ teardown: function() {
+ gBrowser.removeCurrentTab();
+ },
+ run: function* () {
+ setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/");
+ yield setSignedInUser();
+ let tab = yield promiseNewTabLoadEvent("about:accounts");
+ // sign the user out - the tab should have action=signin
+ yield signOut();
+ // wait for the new load.
+ yield promiseOneMessage(tab, "test:document:load");
+ is(tab.linkedBrowser.contentDocument.location.href, "about:accounts?action=signin");
+ }
+},
+{
+ desc: "Test entrypoint query string, no action, no user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* () {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/");
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?entrypoint=abouthome");
+ is(url, "https://example.com/?entrypoint=abouthome", "entrypoint=abouthome got the expected URL");
+ },
+},
+{
+ desc: "Test entrypoint query string for signin",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* () {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ const expected_url = "https://example.com/?is_sign_in";
+ setPref("identity.fxaccounts.remote.signin.uri", expected_url);
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin&entrypoint=abouthome");
+ is(url, expected_url + "&entrypoint=abouthome", "entrypoint=abouthome got the expected URL");
+ },
+},
+{
+ desc: "Test entrypoint query string for signup",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* () {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ const sign_up_url = "https://example.com/?is_sign_up";
+ setPref("identity.fxaccounts.remote.signup.uri", sign_up_url);
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?entrypoint=abouthome&action=signup");
+ is(url, sign_up_url + "&entrypoint=abouthome", "entrypoint=abouthome got the expected URL");
+ },
+},
+{
+ desc: "about:accounts URL params should be copied to remote URL params " +
+ "when remote URL has no URL params, except for 'action'",
+ teardown() {
+ gBrowser.removeCurrentTab();
+ },
+ run: function* () {
+ let signupURL = "https://example.com/";
+ setPref("identity.fxaccounts.remote.signup.uri", signupURL);
+ let queryStr = "email=foo%40example.com&foo=bar&baz=quux";
+ let [, url] =
+ yield promiseNewTabWithIframeLoadEvent("about:accounts?" + queryStr +
+ "&action=action");
+ is(url, signupURL + "?" + queryStr, "URL params are copied to signup URL");
+ },
+},
+{
+ desc: "about:accounts URL params should be copied to remote URL params " +
+ "when remote URL already has some URL params, except for 'action'",
+ teardown() {
+ gBrowser.removeCurrentTab();
+ },
+ run: function* () {
+ let signupURL = "https://example.com/?param";
+ setPref("identity.fxaccounts.remote.signup.uri", signupURL);
+ let queryStr = "email=foo%40example.com&foo=bar&baz=quux";
+ let [, url] =
+ yield promiseNewTabWithIframeLoadEvent("about:accounts?" + queryStr +
+ "&action=action");
+ is(url, signupURL + "&" + queryStr, "URL params are copied to signup URL");
+ },
+},
+]; // gTests
+
+function test()
+{
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info(testCase.desc);
+ try {
+ yield testCase.run();
+ } finally {
+ yield testCase.teardown();
+ }
+ }
+
+ finish();
+ });
+}
+
+function promiseOneMessage(tab, messageName) {
+ let mm = tab.linkedBrowser.messageManager;
+ let deferred = Promise.defer();
+ mm.addMessageListener(messageName, function onmessage(message) {
+ mm.removeMessageListener(messageName, onmessage);
+ deferred.resolve(message);
+ });
+ return deferred.promise;
+}
+
+function promiseNewTabLoadEvent(aUrl)
+{
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ let browser = tab.linkedBrowser;
+ let mm = browser.messageManager;
+
+ // give it an e10s-friendly content script to help with our tests.
+ mm.loadFrameScript(CHROME_BASE + "content_aboutAccounts.js", true);
+ // and wait for it to tell us about the load.
+ return promiseOneMessage(tab, "test:document:load").then(
+ () => tab
+ );
+}
+
+// Returns a promise which is resolved with the iframe's URL after a new
+// tab is created and the iframe in that tab loads.
+function promiseNewTabWithIframeLoadEvent(aUrl) {
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ let browser = tab.linkedBrowser;
+ let mm = browser.messageManager;
+
+ // give it an e10s-friendly content script to help with our tests.
+ mm.loadFrameScript(CHROME_BASE + "content_aboutAccounts.js", true);
+ // and wait for it to tell us about the iframe load.
+ mm.addMessageListener("test:iframe:load", function onFrameLoad(message) {
+ mm.removeMessageListener("test:iframe:load", onFrameLoad);
+ deferred.resolve([tab, message.data.url]);
+ });
+ return deferred.promise;
+}
+
+function checkVisibilities(tab, data) {
+ let ids = Object.keys(data);
+ let mm = tab.linkedBrowser.messageManager;
+ let deferred = Promise.defer();
+ mm.addMessageListener("test:check-visibilities-response", function onResponse(message) {
+ mm.removeMessageListener("test:check-visibilities-response", onResponse);
+ for (let id of ids) {
+ is(message.data[id], data[id], "Element '" + id + "' has correct visibility");
+ }
+ deferred.resolve();
+ });
+ mm.sendAsyncMessage("test:check-visibilities", {ids: ids});
+ return deferred.promise;
+}
+
+// watch out - these will fire observers which if you aren't careful, may
+// interfere with the tests.
+function setSignedInUser(data) {
+ if (!data) {
+ data = {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: true
+ }
+ }
+ return fxAccounts.setSignedInUser(data);
+}
+
+function signOut() {
+ // we always want a "localOnly" signout here...
+ return fxAccounts.signOut(true);
+}
diff --git a/browser/base/content/test/general/browser_aboutCertError.js b/browser/base/content/test/general/browser_aboutCertError.js
new file mode 100644
index 000000000..0e335066c
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutCertError.js
@@ -0,0 +1,409 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This is testing the aboutCertError page (Bug 1207107).
+
+const GOOD_PAGE = "https://example.com/";
+const BAD_CERT = "https://expired.example.com/";
+const UNKNOWN_ISSUER = "https://self-signed.example.com ";
+const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443";
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+add_task(function* checkReturnToAboutHome() {
+ info("Loading a bad cert page directly and making sure 'return to previous page' goes to about:home");
+ let browser;
+ let certErrorLoaded;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
+
+ // Populate the shistory entries manually, since it happens asynchronously
+ // and the following tests will be too soon otherwise.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+
+ info("Clicking the go back button on about:certerror");
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let returnButton = doc.getElementById("returnButton");
+ is(returnButton.getAttribute("autofocus"), "true", "returnButton has autofocus");
+ returnButton.click();
+
+ yield ContentTaskUtils.waitForEvent(this, "pageshow", true);
+ });
+
+ is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
+ is(gBrowser.currentURI.spec, "about:home", "Went back");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkReturnToPreviousPage() {
+ info("Loading a bad cert page and making sure 'return to previous page' goes back");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+ let browser = gBrowser.selectedBrowser;
+
+ info("Loading and waiting for the cert error");
+ let certErrorLoaded = waitForCertErrorLoad(browser);
+ BrowserTestUtils.loadURI(browser, BAD_CERT);
+ yield certErrorLoaded;
+
+ is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
+
+ // Populate the shistory entries manually, since it happens asynchronously
+ // and the following tests will be too soon otherwise.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 2, "there are two shistory entries");
+
+ info("Clicking the go back button on about:certerror");
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let returnButton = doc.getElementById("returnButton");
+ returnButton.click();
+
+ yield ContentTaskUtils.waitForEvent(this, "pageshow", true);
+ });
+
+ is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward");
+ is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkBadStsCert() {
+ info("Loading a badStsCert and making sure exception button doesn't show up");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+ let browser = gBrowser.selectedBrowser;
+
+ info("Loading and waiting for the cert error");
+ let certErrorLoaded = waitForCertErrorLoad(browser);
+ BrowserTestUtils.loadURI(browser, BAD_STS_CERT);
+ yield certErrorLoaded;
+
+ let exceptionButtonHidden = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ return exceptionButton.hidden;
+ });
+ ok(exceptionButtonHidden, "Exception button is hidden");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
+
+add_task(function* checkWrongSystemTimeWarning() {
+ function* setUpPage() {
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ return yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let div = doc.getElementById("wrongSystemTimePanel");
+ let systemDateDiv = doc.getElementById("wrongSystemTime_systemDate");
+ let actualDateDiv = doc.getElementById("wrongSystemTime_actualDate");
+ let learnMoreLink = doc.getElementById("learnMoreLink");
+
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: div.textContent,
+ systemDate: systemDateDiv.textContent,
+ actualDate: actualDateDiv.textContent,
+ learnMoreLink: learnMoreLink.href
+ };
+ });
+ }
+
+ let formatter = new Intl.DateTimeFormat();
+
+ // pretend we have a positively skewed (ahead) system time
+ let serverDate = new Date("2015/10/27");
+ let serverDateFmt = formatter.format(serverDate);
+ let localDateFmt = formatter.format(new Date());
+
+ let skew = Math.floor((Date.now() - serverDate.getTime()) / 1000);
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with a skewed clock");
+ let message = yield Task.spawn(setUpPage);
+
+ isnot(message.divDisplay, "none", "Wrong time message information is visible");
+ ok(message.text.includes("because your clock appears to show the wrong time"),
+ "Correct error message found");
+ ok(message.text.includes("expired.example.com"), "URL found in error message");
+ ok(message.systemDate.includes(localDateFmt), "correct local date displayed");
+ ok(message.actualDate.includes(serverDateFmt), "correct server date displayed");
+ ok(message.learnMoreLink.includes("time-errors"), "time-errors in the Learn More URL");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // pretend we have a negatively skewed (behind) system time
+ serverDate = new Date();
+ serverDate.setYear(serverDate.getFullYear() + 1);
+ serverDateFmt = formatter.format(serverDate);
+
+ skew = Math.floor((Date.now() - serverDate.getTime()) / 1000);
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with a skewed clock");
+ message = yield Task.spawn(setUpPage);
+
+ isnot(message.divDisplay, "none", "Wrong time message information is visible");
+ ok(message.text.includes("because your clock appears to show the wrong time"),
+ "Correct error message found");
+ ok(message.text.includes("expired.example.com"), "URL found in error message");
+ ok(message.systemDate.includes(localDateFmt), "correct local date displayed");
+ ok(message.actualDate.includes(serverDateFmt), "correct server date displayed");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // pretend we only have a slightly skewed system time, four hours
+ skew = 60 * 60 * 4;
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with an only slightly skewed clock");
+ message = yield Task.spawn(setUpPage);
+
+ is(message.divDisplay, "none", "Wrong time message information is not visible");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // now pretend we have no skewed system time
+ skew = 0;
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with no skewed clock");
+ message = yield Task.spawn(setUpPage);
+
+ is(message.divDisplay, "none", "Wrong time message information is not visible");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkAdvancedDetails() {
+ info("Loading a bad cert page and verifying the main error and advanced details section");
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ let message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let shortDescText = doc.getElementById("errorShortDescText");
+ info("Main error text: " + shortDescText.textContent);
+ ok(shortDescText.textContent.includes("expired.example.com"),
+ "Should list hostname in error message.");
+
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+ let el = doc.getElementById("errorCode");
+ return { textContent: el.textContent, tagName: el.tagName };
+ });
+ is(message.textContent, "SEC_ERROR_EXPIRED_CERTIFICATE",
+ "Correct error message found");
+ is(message.tagName, "a", "Error message is a link");
+
+ message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let errorCode = doc.getElementById("errorCode");
+ errorCode.click();
+ let div = doc.getElementById("certificateErrorDebugInformation");
+ let text = doc.getElementById("certificateErrorText");
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let serializable = docShell.failedChannel.securityInfo
+ .QueryInterface(Ci.nsITransportSecurityInfo)
+ .QueryInterface(Ci.nsISerializable);
+ let serializedSecurityInfo = serhelper.serializeToString(serializable);
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: text.textContent,
+ securityInfoAsString: serializedSecurityInfo
+ };
+ });
+ isnot(message.divDisplay, "none", "Debug information is visible");
+ ok(message.text.includes(BAD_CERT), "Correct URL found");
+ ok(message.text.includes("Certificate has expired"),
+ "Correct error message found");
+ ok(message.text.includes("HTTP Strict Transport Security: false"),
+ "Correct HSTS value found");
+ ok(message.text.includes("HTTP Public Key Pinning: false"),
+ "Correct HPKP value found");
+ let certChain = getCertChain(message.securityInfoAsString);
+ ok(message.text.includes(certChain), "Found certificate chain");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkAdvancedDetailsForHSTS() {
+ info("Loading a bad STS cert page and verifying the advanced details section");
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_STS_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ let message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+ let ec = doc.getElementById("errorCode");
+ let cdl = doc.getElementById("cert_domain_link");
+ return {
+ ecTextContent: ec.textContent,
+ ecTagName: ec.tagName,
+ cdlTextContent: cdl.textContent,
+ cdlTagName: cdl.tagName
+ };
+ });
+
+ const badStsUri = Services.io.newURI(BAD_STS_CERT, null, null);
+ is(message.ecTextContent, "SSL_ERROR_BAD_CERT_DOMAIN",
+ "Correct error message found");
+ is(message.ecTagName, "a", "Error message is a link");
+ const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1);
+ is(message.cdlTextContent, url,
+ "Correct cert_domain_link contents found");
+ is(message.cdlTagName, "a", "cert_domain_link is a link");
+
+ message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let errorCode = doc.getElementById("errorCode");
+ errorCode.click();
+ let div = doc.getElementById("certificateErrorDebugInformation");
+ let text = doc.getElementById("certificateErrorText");
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let serializable = docShell.failedChannel.securityInfo
+ .QueryInterface(Ci.nsITransportSecurityInfo)
+ .QueryInterface(Ci.nsISerializable);
+ let serializedSecurityInfo = serhelper.serializeToString(serializable);
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: text.textContent,
+ securityInfoAsString: serializedSecurityInfo
+ };
+ });
+ isnot(message.divDisplay, "none", "Debug information is visible");
+ ok(message.text.includes(badStsUri.spec), "Correct URL found");
+ ok(message.text.includes("requested domain name does not match the server\u2019s certificate"),
+ "Correct error message found");
+ ok(message.text.includes("HTTP Strict Transport Security: false"),
+ "Correct HSTS value found");
+ ok(message.text.includes("HTTP Public Key Pinning: true"),
+ "Correct HPKP value found");
+ let certChain = getCertChain(message.securityInfoAsString);
+ ok(message.text.includes(certChain), "Found certificate chain");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkUnknownIssuerLearnMoreLink() {
+ info("Loading a cert error for self-signed pages and checking the correct link is shown");
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(UNKNOWN_ISSUER);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ let href = yield ContentTask.spawn(browser, null, function* () {
+ let learnMoreLink = content.document.getElementById("learnMoreLink");
+ return learnMoreLink.href;
+ });
+ ok(href.endsWith("security-error"), "security-error in the Learn More URL");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+function waitForCertErrorLoad(browser) {
+ return new Promise(resolve => {
+ info("Waiting for DOMContentLoaded event");
+ browser.addEventListener("DOMContentLoaded", function load() {
+ browser.removeEventListener("DOMContentLoaded", load, false, true);
+ resolve();
+ }, false, true);
+ });
+}
+
+function getCertChain(securityInfoAsString) {
+ let certChain = "";
+ const serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let securityInfo = serhelper.deserializeObject(securityInfoAsString);
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ let certs = securityInfo.failedCertChain.getEnumerator();
+ while (certs.hasMoreElements()) {
+ let cert = certs.getNext();
+ cert.QueryInterface(Ci.nsIX509Cert);
+ certChain += getPEMString(cert);
+ }
+ return certChain;
+}
+
+function getDERString(cert)
+{
+ var length = {};
+ var derArray = cert.getRawDER(length);
+ var derString = '';
+ for (var i = 0; i < derArray.length; i++) {
+ derString += String.fromCharCode(derArray[i]);
+ }
+ return derString;
+}
+
+function getPEMString(cert)
+{
+ var derb64 = btoa(getDERString(cert));
+ // Wrap the Base64 string into lines of 64 characters,
+ // with CRLF line breaks (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return "-----BEGIN CERTIFICATE-----\r\n"
+ + wrapped
+ + "\r\n-----END CERTIFICATE-----\r\n";
+}
diff --git a/browser/base/content/test/general/browser_aboutHealthReport.js b/browser/base/content/test/general/browser_aboutHealthReport.js
new file mode 100644
index 000000000..0be184fb8
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutHealthReport.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+const HTTPS_BASE = "https://example.com/browser/browser/base/content/test/general/";
+
+const TELEMETRY_LOG_PREF = "toolkit.telemetry.log.level";
+const telemetryOriginalLogPref = Preferences.get(TELEMETRY_LOG_PREF, null);
+
+const originalReportUrl = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrl");
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ if (telemetryOriginalLogPref) {
+ Preferences.set(TELEMETRY_LOG_PREF, telemetryOriginalLogPref);
+ } else {
+ Preferences.reset(TELEMETRY_LOG_PREF);
+ }
+
+ try {
+ Services.prefs.setCharPref("datareporting.healthreport.about.reportUrl", originalReportUrl);
+ Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true);
+ } catch (ex) {}
+});
+
+function fakeTelemetryNow(...args) {
+ let date = new Date(...args);
+ let scope = {};
+ const modules = [
+ Cu.import("resource://gre/modules/TelemetrySession.jsm", scope),
+ Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", scope),
+ Cu.import("resource://gre/modules/TelemetryController.jsm", scope),
+ ];
+
+ for (let m of modules) {
+ m.Policy.now = () => new Date(date);
+ }
+
+ return date;
+}
+
+function* setupPingArchive() {
+ let scope = {};
+ Cu.import("resource://gre/modules/TelemetryController.jsm", scope);
+ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript(CHROME_BASE + "healthreport_pingData.js", scope);
+
+ for (let p of scope.TEST_PINGS) {
+ fakeTelemetryNow(p.date);
+ p.id = yield scope.TelemetryController.submitExternalPing(p.type, p.payload);
+ }
+}
+
+var gTests = [
+
+{
+ desc: "Test the remote commands",
+ setup: Task.async(function*()
+ {
+ Preferences.set(TELEMETRY_LOG_PREF, "Trace");
+ yield setupPingArchive();
+ Preferences.set("datareporting.healthreport.about.reportUrl",
+ HTTPS_BASE + "healthreport_testRemoteCommands.html");
+ }),
+ run: function (iframe)
+ {
+ let deferred = Promise.defer();
+ let results = 0;
+ try {
+ iframe.contentWindow.addEventListener("FirefoxHealthReportTestResponse", function evtHandler(event) {
+ let data = event.detail.data;
+ if (data.type == "testResult") {
+ ok(data.pass, data.info);
+ results++;
+ }
+ else if (data.type == "testsComplete") {
+ is(results, data.count, "Checking number of results received matches the number of tests that should have run");
+ iframe.contentWindow.removeEventListener("FirefoxHealthReportTestResponse", evtHandler, true);
+ deferred.resolve();
+ }
+ }, true);
+
+ } catch (e) {
+ ok(false, "Failed to get all commands");
+ deferred.reject();
+ }
+ return deferred.promise;
+ }
+},
+
+]; // gTests
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // xxxmpc leaving this here until we resolve bug 854038 and bug 854060
+ requestLongerTimeout(10);
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info(testCase.desc);
+ yield testCase.setup();
+
+ let iframe = yield promiseNewTabLoadEvent("about:healthreport");
+
+ yield testCase.run(iframe);
+
+ gBrowser.removeCurrentTab();
+ }
+
+ finish();
+ });
+}
+
+function promiseNewTabLoadEvent(aUrl, aEventType="load")
+{
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ tab.linkedBrowser.addEventListener(aEventType, function load(event) {
+ tab.linkedBrowser.removeEventListener(aEventType, load, true);
+ let iframe = tab.linkedBrowser.contentDocument.getElementById("remote-report");
+ iframe.addEventListener("load", function frameLoad(e) {
+ if (iframe.contentWindow.location.href == "about:blank" ||
+ e.target != iframe) {
+ return;
+ }
+ iframe.removeEventListener("load", frameLoad, false);
+ deferred.resolve(iframe);
+ }, false);
+ }, true);
+ return deferred.promise;
+}
diff --git a/browser/base/content/test/general/browser_aboutHome.js b/browser/base/content/test/general/browser_aboutHome.js
new file mode 100644
index 000000000..f0e19e852
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -0,0 +1,668 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test needs to be split up. See bug 1258717.
+requestLongerTimeout(4);
+ignoreAllUncaughtExceptions();
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHome.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/" +
+ "content/test/general/aboutHome_content_script.js";
+var gRightsVersion = Services.prefs.getIntPref("browser.rights.version");
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ Services.prefs.clearUserPref("network.cookies.cookieBehavior");
+ Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
+ Services.prefs.clearUserPref("browser.rights.override");
+ Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
+});
+
+add_task(function* () {
+ info("Check that clearing cookies does not clear storage");
+
+ yield withSnippetsMap(
+ () => {
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .notifyObservers(null, "cookie-changed", "cleared");
+ },
+ function* () {
+ isnot(content.gSnippetsMap.get("snippets-last-update"), null,
+ "snippets-last-update should have a value");
+ });
+});
+
+add_task(function* () {
+ info("Check default snippets are shown");
+
+ yield withSnippetsMap(null, function* () {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element")
+ is(snippetsElt.getElementsByTagName("span").length, 1,
+ "A default snippet is present.");
+ });
+});
+
+add_task(function* () {
+ info("Check default snippets are shown if snippets are invalid xml");
+
+ yield withSnippetsMap(
+ // This must set some incorrect xhtml code.
+ snippetsMap => snippetsMap.set("snippets", "<p><b></p></b>"),
+ function* () {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.getElementsByTagName("span").length, 1,
+ "A default snippet is present.");
+
+ content.gSnippetsMap.delete("snippets");
+ });
+});
+
+add_task(function* () {
+ info("Check that performing a search fires a search event and records to Telemetry.");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ let currEngine = Services.search.currentEngine;
+ let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+ // Make this actually work in healthreport by giving it an ID:
+ Object.defineProperty(engine.wrappedJSObject, "identifier",
+ { value: "org.mozilla.testsearchsuggestions" });
+
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.currentEngine = engine;
+ yield p;
+
+ yield ContentTask.spawn(browser, { expectedName: engine.name }, function* (args) {
+ let engineName = content.wrappedJSObject.gContentSearchController.defaultEngine.name;
+ is(engineName, args.expectedName, "Engine name in DOM should match engine we just added");
+ });
+
+ let numSearchesBefore = 0;
+ // Get the current number of recorded searches.
+ let histogramKey = engine.identifier + ".abouthome";
+ try {
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ if (histogramKey in hs) {
+ numSearchesBefore = hs[histogramKey].sum;
+ }
+ } catch (ex) {
+ // No searches performed yet, not a problem, |numSearchesBefore| is 0.
+ }
+
+ let searchStr = "a search";
+
+ let expectedURL = Services.search.currentEngine
+ .getSubmission(searchStr, null, "homepage").uri.spec;
+ let promise = waitForDocLoadAndStopIt(expectedURL, browser);
+
+ // Perform a search to increase the SEARCH_COUNT histogram.
+ yield ContentTask.spawn(browser, { searchStr }, function* (args) {
+ let doc = content.document;
+ info("Perform a search.");
+ doc.getElementById("searchText").value = args.searchStr;
+ doc.getElementById("searchSubmit").click();
+ });
+
+ yield promise;
+
+ // Make sure the SEARCH_COUNTS histogram has the right key and count.
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ Assert.ok(histogramKey in hs, "histogram with key should be recorded");
+ Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
+ "histogram sum should be incremented");
+
+ Services.search.currentEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) {}
+ });
+});
+
+add_task(function* () {
+ info("Check snippets map is cleared if cached version is old");
+
+ yield withSnippetsMap(
+ snippetsMap => {
+ snippetsMap.set("snippets", "test");
+ snippetsMap.set("snippets-cached-version", 0);
+ },
+ function* () {
+ let snippetsMap = content.gSnippetsMap;
+ ok(!snippetsMap.has("snippets"), "snippets have been properly cleared");
+ ok(!snippetsMap.has("snippets-cached-version"),
+ "cached-version has been properly cleared");
+ });
+});
+
+add_task(function* () {
+ info("Check cached snippets are shown if cached version is current");
+
+ yield withSnippetsMap(
+ snippetsMap => snippetsMap.set("snippets", "test"),
+ function* (args) {
+ let doc = content.document;
+ let snippetsMap = content.gSnippetsMap
+
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.innerHTML, "test", "Cached snippet is present.");
+
+ is(snippetsMap.get("snippets"), "test", "snippets still cached");
+ is(snippetsMap.get("snippets-cached-version"),
+ args.expectedVersion,
+ "cached-version is correct");
+ ok(snippetsMap.has("snippets-last-update"), "last-update still exists");
+ }, { expectedVersion: AboutHomeUtils.snippetsVersion });
+});
+
+add_task(function* () {
+ info("Check if the 'Know Your Rights' default snippet is shown when " +
+ "'browser.rights.override' pref is set and that its link works");
+
+ Services.prefs.setBoolPref("browser.rights.override", false);
+
+ ok(AboutHomeUtils.showKnowYourRights, "AboutHomeUtils.showKnowYourRights should be TRUE");
+
+ yield withSnippetsMap(null, function* () {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ let linkEl = snippetsElt.querySelector("a");
+ is(linkEl.href, "about:rights", "Snippet link is present.");
+ }, null, function* () {
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:rights");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a[href='about:rights']", {
+ button: 0
+ }, gBrowser.selectedBrowser);
+ yield loadPromise;
+ is(gBrowser.currentURI.spec, "about:rights", "about:rights should have opened.");
+ });
+
+
+ Services.prefs.clearUserPref("browser.rights.override");
+});
+
+add_task(function* () {
+ info("Check if the 'Know Your Rights' default snippet is NOT shown when " +
+ "'browser.rights.override' pref is NOT set");
+
+ Services.prefs.setBoolPref("browser.rights.override", true);
+
+ let rightsData = AboutHomeUtils.knowYourRightsData;
+ ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE");
+
+ yield withSnippetsMap(null, function*() {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights",
+ "Snippet link should not point to about:rights.");
+ });
+
+ Services.prefs.clearUserPref("browser.rights.override");
+});
+
+add_task(function* () {
+ info("Check POST search engine support");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ return new Promise(resolve => {
+ let searchObserver = Task.async(function* search_observer(subject, topic, data) {
+ let currEngine = Services.search.defaultEngine;
+ let engine = subject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + data + " for " + engine.name);
+
+ if (data != "engine-added")
+ return;
+
+ if (engine.name != "POST Search")
+ return;
+
+ Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+
+ // Ready to execute the tests!
+ let needle = "Search for something awesome.";
+
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.defaultEngine = engine;
+ yield p;
+
+ let promise = BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, { needle }, function* (args) {
+ let doc = content.document;
+ doc.getElementById("searchText").value = args.needle;
+ doc.getElementById("searchSubmit").click();
+ });
+
+ yield promise;
+
+ // When the search results load, check them for correctness.
+ yield ContentTask.spawn(browser, { needle }, function* (args) {
+ let loadedText = content.document.body.textContent;
+ ok(loadedText, "search page loaded");
+ is(loadedText, "searchterms=" + escape(args.needle.replace(/\s/g, "+")),
+ "Search text should arrive correctly");
+ });
+
+ Services.search.defaultEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) {}
+ resolve();
+ });
+ Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+ Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
+ null, null, false);
+ });
+ });
+});
+
+add_task(function* () {
+ info("Make sure that a page can't imitate about:home");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ let promise = BrowserTestUtils.browserLoaded(browser);
+ browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html");
+ yield promise;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let button = content.document.getElementById("settings");
+ ok(button, "Found settings button in test page");
+ button.click();
+ });
+
+ yield new Promise(resolve => {
+ // It may take a few turns of the event loop before the window
+ // is displayed, so we wait.
+ function check(n) {
+ let win = Services.wm.getMostRecentWindow("Browser:Preferences");
+ ok(!win, "Preferences window not showing");
+ if (win) {
+ win.close();
+ }
+
+ if (n > 0) {
+ executeSoon(() => check(n-1));
+ } else {
+ resolve();
+ }
+ }
+
+ check(5);
+ });
+ });
+});
+
+add_task(function* () {
+ // See browser_contentSearchUI.js for comprehensive content search UI tests.
+ info("Search suggestion smoke test");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ // Add a test engine that provides suggestions and switch to it.
+ let currEngine = Services.search.currentEngine;
+ let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.currentEngine = engine;
+ yield p;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // Avoid intermittent failures.
+ content.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
+
+ // Type an X in the search input.
+ let input = content.document.getElementById("searchText");
+ input.focus();
+ });
+
+ yield BrowserTestUtils.synthesizeKey("x", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // Wait for the search suggestions to become visible.
+ let table = content.document.getElementById("searchSuggestionTable");
+ let input = content.document.getElementById("searchText");
+
+ yield new Promise(resolve => {
+ let observer = new content.MutationObserver(() => {
+ if (input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ ok(!table.hidden, "Search suggestion table unhidden");
+ resolve();
+ }
+ });
+ observer.observe(input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+ });
+ });
+
+ // Empty the search input, causing the suggestions to be hidden.
+ yield BrowserTestUtils.synthesizeKey("a", { accelKey: true }, browser);
+ yield BrowserTestUtils.synthesizeKey("VK_DELETE", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let table = content.document.getElementById("searchSuggestionTable");
+ yield ContentTaskUtils.waitForCondition(() => table.hidden,
+ "Search suggestion table hidden");
+ });
+
+ Services.search.currentEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) { }
+ });
+});
+
+add_task(function* () {
+ info("Clicking suggestion list while composing");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ // Add a test engine that provides suggestions and switch to it.
+ let currEngine = Services.search.currentEngine;
+ let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.currentEngine = engine;
+ yield p;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // Start composition and type "x"
+ let input = content.document.getElementById("searchText");
+ input.focus();
+ });
+
+ yield BrowserTestUtils.synthesizeComposition({
+ type: "compositionstart",
+ data: ""
+ }, browser);
+ yield BrowserTestUtils.synthesizeCompositionChange({
+ composition: {
+ string: "x",
+ clauses: [
+ { length: 1, attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ }, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let searchController = content.wrappedJSObject.gContentSearchController;
+
+ // Wait for the search suggestions to become visible.
+ let table = searchController._suggestionsList;
+ let input = content.document.getElementById("searchText");
+
+ yield new Promise(resolve => {
+ let observer = new content.MutationObserver(() => {
+ if (input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ ok(!table.hidden, "Search suggestion table unhidden");
+ resolve();
+ }
+ });
+ observer.observe(input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+ });
+
+ let row = table.children[1];
+ row.setAttribute("id", "TEMPID");
+
+ // ContentSearchUIController looks at the current selectedIndex when
+ // performing a search. Synthesizing the mouse event on the suggestion
+ // doesn't actually mouseover the suggestion and trigger it to be flagged
+ // as selected, so we manually select it first.
+ searchController.selectedIndex = 1;
+ });
+
+ // Click the second suggestion.
+ let expectedURL = Services.search.currentEngine
+ .getSubmission("xbar", null, "homepage").uri.spec;
+ let loadPromise = waitForDocLoadAndStopIt(expectedURL);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#TEMPID", {
+ button: 0
+ }, browser);
+ yield loadPromise;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let input = content.document.getElementById("searchText");
+ ok(input.value == "x", "Input value did not change");
+
+ let row = content.document.getElementById("TEMPID");
+ if (row) {
+ row.removeAttribute("id");
+ }
+ });
+
+ Services.search.currentEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) { }
+ });
+});
+
+add_task(function* () {
+ info("Pressing any key should focus the search box in the page, and send the key to it");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ isnot(doc.getElementById("searchText"), doc.activeElement,
+ "Search input should not be the active element.");
+ });
+
+ yield BrowserTestUtils.synthesizeKey("a", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let searchInput = doc.getElementById("searchText");
+ yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput,
+ "Search input should be the active element.");
+ is(searchInput.value, "a", "Search input should be 'a'.");
+ });
+ });
+});
+
+add_task(function* () {
+ info("Cmd+k should focus the search box in the toolbar when it's present");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+
+ let doc = window.document;
+ let searchInput = doc.getElementById("searchbar").textbox.inputField;
+ isnot(searchInput, doc.activeElement, "Search bar should not be the active element.");
+
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ yield promiseWaitForCondition(() => doc.activeElement === searchInput);
+ is(searchInput, doc.activeElement, "Search bar should be the active element.");
+ });
+});
+
+add_task(function* () {
+ info("Sync button should open about:preferences#sync");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ let oldOpenPrefs = window.openPreferences;
+ let openPrefsPromise = new Promise(resolve => {
+ window.openPreferences = function (pane, params) {
+ resolve({ pane: pane, params: params });
+ };
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#sync", {}, browser);
+
+ let result = yield openPrefsPromise;
+ window.openPreferences = oldOpenPrefs;
+
+ is(result.pane, "paneSync", "openPreferences should be called with paneSync");
+ is(result.params.urlParams.entrypoint, "abouthome",
+ "openPreferences should be called with abouthome entrypoint");
+ });
+});
+
+add_task(function* () {
+ info("Pressing Space while the Addons button is focused should activate it");
+
+ // Skip this test on Mac, because Space doesn't activate the button there.
+ if (AppConstants.platform == "macosx") {
+ return;
+ }
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ info("Waiting for about:addons tab to open...");
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let addOnsButton = content.document.getElementById("addons");
+ addOnsButton.focus();
+ });
+ yield BrowserTestUtils.synthesizeKey(" ", {}, browser);
+
+ let tab = yield promiseTabOpened;
+ is(tab.linkedBrowser.currentURI.spec, "about:addons",
+ "Should have seen the about:addons tab");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+/**
+ * Cleans up snippets and ensures that by default we don't try to check for
+ * remote snippets since that may cause network bustage or slowness.
+ *
+ * @param aSetupFn
+ * The setup function to be run.
+ * @param testFn
+ * the content task to run
+ * @param testArgs (optional)
+ * the parameters to pass to the content task
+ * @param parentFn (optional)
+ * the function to run in the parent after the content task has completed.
+ * @return {Promise} resolved when the snippets are ready. Gets the snippets map.
+ */
+function* withSnippetsMap(setupFn, testFn, testArgs = null, parentFn = null) {
+ let setupFnSource;
+ if (setupFn) {
+ setupFnSource = setupFn.toSource();
+ }
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ let promiseAfterLocationChange = () => {
+ return ContentTask.spawn(browser, {
+ setupFnSource,
+ version: AboutHomeUtils.snippetsVersion,
+ }, function* (args) {
+ return new Promise(resolve => {
+ let document = content.document;
+ // We're not using Promise-based listeners, because they resolve asynchronously.
+ // The snippets test setup code relies on synchronous behaviour here.
+ document.addEventListener("AboutHomeLoadSnippets", function loadSnippets() {
+ document.removeEventListener("AboutHomeLoadSnippets", loadSnippets);
+
+ let updateSnippets;
+ if (args.setupFnSource) {
+ updateSnippets = eval(`(() => (${args.setupFnSource}))()`);
+ }
+
+ content.wrappedJSObject.ensureSnippetsMapThen(snippetsMap => {
+ snippetsMap = Cu.waiveXrays(snippetsMap);
+ info("Got snippets map: " +
+ "{ last-update: " + snippetsMap.get("snippets-last-update") +
+ ", cached-version: " + snippetsMap.get("snippets-cached-version") +
+ " }");
+ // Don't try to update.
+ snippetsMap.set("snippets-last-update", Date.now());
+ snippetsMap.set("snippets-cached-version", args.version);
+ // Clear snippets.
+ snippetsMap.delete("snippets");
+
+ if (updateSnippets) {
+ updateSnippets(snippetsMap);
+ }
+
+ // Tack it to the global object
+ content.gSnippetsMap = snippetsMap;
+
+ resolve();
+ });
+ });
+ });
+ });
+ };
+
+ // We'd like to listen to the 'AboutHomeLoadSnippets' event on a fresh
+ // document as soon as technically possible, so we use webProgress.
+ let promise = new Promise(resolve => {
+ let wpl = {
+ onLocationChange() {
+ gBrowser.removeProgressListener(wpl);
+ // Phase 2: retrieving the snippets map is the next promise on our agenda.
+ promiseAfterLocationChange().then(resolve);
+ },
+ onProgressChange() {},
+ onStatusChange() {},
+ onSecurityChange() {}
+ };
+ gBrowser.addProgressListener(wpl);
+ });
+
+ // Set the URL to 'about:home' here to allow capturing the 'AboutHomeLoadSnippets'
+ // event.
+ browser.loadURI("about:home");
+ // Wait for LocationChange.
+ yield promise;
+
+ yield ContentTask.spawn(browser, testArgs, testFn);
+ if (parentFn) {
+ yield parentFn();
+ }
+ });
+}
+
+function promiseContentSearchChange(browser, newEngineName) {
+ return ContentTask.spawn(browser, { newEngineName }, function* (args) {
+ return new Promise(resolve => {
+ content.addEventListener("ContentSearchService", function listener(aEvent) {
+ if (aEvent.detail.type == "CurrentState" &&
+ content.wrappedJSObject.gContentSearchController.defaultEngine.name == args.newEngineName) {
+ content.removeEventListener("ContentSearchService", listener);
+ resolve();
+ }
+ });
+ });
+ });
+}
+
+function promiseNewEngine(basename) {
+ info("Waiting for engine to be added: " + basename);
+ return new Promise((resolve, reject) => {
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ registerCleanupFunction(() => {
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) { /* Can't remove the engine more than once */ }
+ });
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js b/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js
new file mode 100644
index 000000000..bfe0fe9c8
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js
@@ -0,0 +1,28 @@
+add_task(function* () {
+ let newWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let resizedPromise = BrowserTestUtils.waitForEvent(newWindow, "resize");
+ newWindow.resizeTo(300, 300);
+ yield resizedPromise;
+
+ yield BrowserTestUtils.openNewForegroundTab(newWindow.gBrowser, "about:home");
+
+ yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
+ Assert.equal(content.document.body.getAttribute("narrow"), "true", "narrow mode");
+ });
+
+ resizedPromise = BrowserTestUtils.waitForContentEvent(newWindow.gBrowser.selectedBrowser, "resize");
+
+
+ yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
+ content.window.resizeTo(800, 800);
+ });
+
+ yield resizedPromise;
+
+ yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
+ Assert.equal(content.document.body.hasAttribute("narrow"), false, "non-narrow mode");
+ });
+
+ yield BrowserTestUtils.closeWindow(newWindow);
+});
diff --git a/browser/base/content/test/general/browser_aboutNetError.js b/browser/base/content/test/general/browser_aboutNetError.js
new file mode 100644
index 000000000..5185cbcaa
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutNetError.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Set ourselves up for TLS error
+Services.prefs.setIntPref("security.tls.version.max", 3);
+Services.prefs.setIntPref("security.tls.version.min", 3);
+
+const LOW_TLS_VERSION = "https://tls1.example.com/";
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+add_task(function* checkReturnToPreviousPage() {
+ info("Loading a TLS page that isn't supported, ensure we have a fix button and clicking it then loads the page");
+ let browser;
+ let pageLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(LOW_TLS_VERSION);
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ }, false);
+
+ info("Loading and waiting for the net error");
+ yield pageLoaded;
+
+ // NB: This code assumes that the error page and the test page load in the
+ // same process. If this test starts to fail, it could be because they load
+ // in different processes.
+ yield ContentTask.spawn(browser, LOW_TLS_VERSION, function* (LOW_TLS_VERSION_) {
+ ok(content.document.getElementById("prefResetButton").getBoundingClientRect().left >= 0,
+ "Should have a visible button");
+
+ ok(content.document.documentURI.startsWith("about:neterror"), "Should be showing error page");
+
+ let doc = content.document;
+ let prefResetButton = doc.getElementById("prefResetButton");
+ is(prefResetButton.getAttribute("autofocus"), "true", "prefResetButton has autofocus");
+ prefResetButton.click();
+
+ yield ContentTaskUtils.waitForEvent(this, "pageshow", true);
+
+ is(content.document.documentURI, LOW_TLS_VERSION_, "Should not be showing page");
+ });
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js b/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js
new file mode 100644
index 000000000..e574ba978
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: window.location is null");
+
+
+add_task(function* checkIdentityOfAboutSupport() {
+ let tab = gBrowser.loadOneTab("about:support", {
+ referrerURI: null,
+ inBackground: false,
+ allowThirdPartyFixup: false,
+ relatedToCurrent: false,
+ skipAnimation: true,
+ allowMixedContent: false
+ });
+
+ yield promiseTabLoaded(tab);
+ let identityBox = document.getElementById("identity-box");
+ is(identityBox.className, "chromeUI", "Should know that we're chrome.");
+ gBrowser.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/general/browser_accesskeys.js b/browser/base/content/test/general/browser_accesskeys.js
new file mode 100644
index 000000000..56fe3995f
--- /dev/null
+++ b/browser/base/content/test/general/browser_accesskeys.js
@@ -0,0 +1,82 @@
+add_task(function *() {
+ yield pushPrefs(["ui.key.contentAccess", 5], ["ui.key.chromeAccess", 5]);
+
+ const gPageURL1 = "data:text/html,<body><p>" +
+ "<button id='button' accesskey='y'>Button</button>" +
+ "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" +
+ "</p></body>";
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1);
+ tab1.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+ Services.focus.clearFocus(window);
+
+ // Press an accesskey in the child document while the chrome is focused.
+ let focusedId = yield performAccessKey("y");
+ is(focusedId, "button", "button accesskey");
+
+ // Press an accesskey in the child document while the content document is focused.
+ focusedId = yield performAccessKey("z");
+ is(focusedId, "checkbox", "checkbox accesskey");
+
+ // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused.
+ let newButton = document.createElement("button");
+ newButton.id = "chromebutton";
+ newButton.setAttribute("accesskey", "z");
+ document.documentElement.appendChild(newButton);
+
+ Services.focus.clearFocus(window);
+
+ focusedId = yield performAccessKeyForChrome("z");
+ is(focusedId, "chromebutton", "chromebutton accesskey");
+
+ // Add a second tab and ensure that accesskey from the first tab is not used.
+ const gPageURL2 = "data:text/html,<body>" +
+ "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" +
+ "</body>";
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2);
+ tab2.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+ Services.focus.clearFocus(window);
+
+ focusedId = yield performAccessKey("y");
+ is(focusedId, "tab2button", "button accesskey in tab2");
+
+ // Press the accesskey for the chrome element while the content document is focused.
+ focusedId = yield performAccessKeyForChrome("z");
+ is(focusedId, "chromebutton", "chromebutton accesskey");
+
+ newButton.parentNode.removeChild(newButton);
+
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+});
+
+function childHandleFocus() {
+ content.document.body.firstChild.addEventListener("focus", function focused(event) {
+ let focusedElement = content.document.activeElement;
+ focusedElement.blur();
+ sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id })
+ }, true);
+}
+
+function performAccessKey(key)
+{
+ return new Promise(resolve => {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("Test:FocusFromAccessKey", function listenForFocus(msg) {
+ mm.removeMessageListener("Test:FocusFromAccessKey", listenForFocus);
+ resolve(msg.data.focus);
+ });
+
+ EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
+ });
+}
+
+// This version is used when a chrome elemnt is expected to be found for an accesskey.
+function* performAccessKeyForChrome(key, inChild)
+{
+ let waitFocusChangePromise = BrowserTestUtils.waitForEvent(document, "focus", true);
+ EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
+ yield waitFocusChangePromise;
+ return document.activeElement.id;
+}
diff --git a/browser/base/content/test/general/browser_addCertException.js b/browser/base/content/test/general/browser_addCertException.js
new file mode 100644
index 000000000..e2cf34b47
--- /dev/null
+++ b/browser/base/content/test/general/browser_addCertException.js
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test adding a certificate exception by attempting to browse to a site with
+// a bad certificate, being redirected to the internal about:certerror page,
+// using the button contained therein to load the certificate exception
+// dialog, using that to add an exception, and finally successfully visiting
+// the site, including showing the right identity box and control center icons.
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ yield loadBadCertPage("https://expired.example.com");
+ checkControlPanelIcons();
+ let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("expired.example.com", -1);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Check for the correct icons in the identity box and control center.
+function checkControlPanelIcons() {
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityBox.click();
+ document.getElementById("identity-popup-security-expander").click();
+
+ is_element_visible(document.getElementById("connection-icon"), "Should see connection icon");
+ let connectionIconImage = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("connection-icon"), "")
+ .getPropertyValue("list-style-image");
+ let securityViewBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
+ .getPropertyValue("background-image");
+ let securityContentBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
+ .getPropertyValue("background-image");
+ is(connectionIconImage,
+ "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using expected icon image in the identity block");
+ is(securityViewBG,
+ "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using expected icon image in the Control Center main view");
+ is(securityContentBG,
+ "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using expected icon image in the Control Center subview");
+
+ gIdentityHandler._identityPopup.hidden = true;
+}
+
diff --git a/browser/base/content/test/general/browser_addKeywordSearch.js b/browser/base/content/test/general/browser_addKeywordSearch.js
new file mode 100644
index 000000000..f38050b43
--- /dev/null
+++ b/browser/base/content/test/general/browser_addKeywordSearch.js
@@ -0,0 +1,81 @@
+var testData = [
+ { desc: "No path",
+ action: "http://example.com/",
+ param: "q",
+ },
+ { desc: "With path",
+ action: "http://example.com/new-path-here/",
+ param: "q",
+ },
+ { desc: "No action",
+ action: "",
+ param: "q",
+ },
+ { desc: "With Query String",
+ action: "http://example.com/search?oe=utf-8",
+ param: "q",
+ },
+];
+
+add_task(function*() {
+ const TEST_URL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let count = 0;
+ for (let method of ["GET", "POST"]) {
+ for (let {desc, action, param } of testData) {
+ info(`Running ${method} keyword test '${desc}'`);
+ let id = `keyword-form-${count++}`;
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenuPromise =
+ BrowserTestUtils.waitForEvent(contextMenu, "popupshown")
+ .then(() => gContextMenuContentData.popupNode);
+
+ yield ContentTask.spawn(tab.linkedBrowser,
+ { action, param, method, id }, function* (args) {
+ let doc = content.document;
+ let form = doc.createElement("form");
+ form.id = args.id;
+ form.method = args.method;
+ form.action = args.action;
+ let element = doc.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("name", args.param);
+ form.appendChild(element);
+ doc.body.appendChild(form);
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter(`#${id} > input`,
+ { type : "contextmenu", button : 2 },
+ tab.linkedBrowser);
+ let target = yield contextMenuPromise;
+
+ yield new Promise(resolve => {
+ let url = action || tab.linkedBrowser.currentURI.spec;
+ let mm = tab.linkedBrowser.messageManager;
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+ if (method == "GET") {
+ ok(message.data.spec.endsWith(`${param}=%s`),
+ `Check expected url for field named ${param} and action ${action}`);
+ } else {
+ is(message.data.spec, url,
+ `Check expected url for field named ${param} and action ${action}`);
+ is(message.data.postData, `${param}%3D%25s`,
+ `Check expected POST data for field named ${param} and action ${action}`);
+ }
+ resolve();
+ };
+ mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+ mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", null, { target });
+ });
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_alltabslistener.js b/browser/base/content/test/general/browser_alltabslistener.js
new file mode 100644
index 000000000..a56473ec9
--- /dev/null
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -0,0 +1,206 @@
+var Ci = Components.interfaces;
+
+const gCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+var gFrontProgressListener = {
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("FrontProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var state = "onLocationChange";
+ info("FrontProgress: " + state + " " + aLocationURI.spec);
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("FrontProgress: " + state + " 0x" + aState.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ }
+}
+
+var gAllProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("AllProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+
+ if ((aStateFlags & gCompleteState) == gCompleteState) {
+ ok(gAllNotificationsPos == gAllNotifications.length, "Saw the expected number of notifications");
+ ok(gFrontNotificationsPos == gFrontNotifications.length, "Saw the expected number of frontnotifications");
+ executeSoon(gNextTest);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ var state = "onLocationChange";
+ info("AllProgress: " + state + " " + aLocationURI.spec);
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ },
+
+ onStatusChange: function (aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
+ var state = "onStatusChange";
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ },
+
+ onSecurityChange: function (aBrowser, aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("AllProgress: " + state + " 0x" + aState.toString(16));
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ }
+}
+
+var gFrontNotifications, gAllNotifications, gFrontNotificationsPos, gAllNotificationsPos;
+var gBackgroundTab, gForegroundTab, gBackgroundBrowser, gForegroundBrowser, gTestBrowser;
+var gTestPage = "/browser/browser/base/content/test/general/alltabslistener.html";
+const kBasePage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+var gNextTest;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBackgroundTab = gBrowser.addTab();
+ gForegroundTab = gBrowser.addTab();
+ gBackgroundBrowser = gBrowser.getBrowserForTab(gBackgroundTab);
+ gForegroundBrowser = gBrowser.getBrowserForTab(gForegroundTab);
+ gBrowser.selectedTab = gForegroundTab;
+
+ // We must wait until a page has completed loading before
+ // starting tests or we get notifications from that
+ let promises = [
+ waitForDocLoadComplete(gBackgroundBrowser),
+ waitForDocLoadComplete(gForegroundBrowser)
+ ];
+ gBackgroundBrowser.loadURI(kBasePage);
+ gForegroundBrowser.loadURI(kBasePage);
+ Promise.all(promises).then(startTest1);
+}
+
+function runTest(browser, url, next) {
+ gFrontNotificationsPos = 0;
+ gAllNotificationsPos = 0;
+ gNextTest = next;
+ gTestBrowser = browser;
+ browser.loadURI(url);
+}
+
+function startTest1() {
+ info("\nTest 1");
+ gBrowser.addProgressListener(gFrontProgressListener);
+ gBrowser.addTabsProgressListener(gAllProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
+}
+
+function startTest2() {
+ info("\nTest 2");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
+}
+
+function startTest3() {
+ info("\nTest 3");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
+}
+
+function startTest4() {
+ info("\nTest 4");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
+}
+
+function startTest5() {
+ info("\nTest 5");
+ // Switch the foreground browser
+ [gForegroundBrowser, gBackgroundBrowser] = [gBackgroundBrowser, gForegroundBrowser];
+ [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
+ // Avoid the onLocationChange this will fire
+ gBrowser.removeProgressListener(gFrontProgressListener);
+ gBrowser.selectedTab = gForegroundTab;
+ gBrowser.addProgressListener(gFrontProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
+}
+
+function startTest6() {
+ info("\nTest 6");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
+}
+
+function finishTest() {
+ gBrowser.removeProgressListener(gFrontProgressListener);
+ gBrowser.removeTabsProgressListener(gAllProgressListener);
+ gBrowser.removeTab(gBackgroundTab);
+ gBrowser.removeTab(gForegroundTab);
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_audioTabIcon.js b/browser/base/content/test/general/browser_audioTabIcon.js
new file mode 100644
index 000000000..4d7a7bbd8
--- /dev/null
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -0,0 +1,504 @@
+const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html";
+const TABATTR_REMOVAL_PREFNAME = "browser.tabs.delayHidingAudioPlayingIconMS";
+const INITIAL_TABATTR_REMOVAL_DELAY_MS = Services.prefs.getIntPref(TABATTR_REMOVAL_PREFNAME);
+
+function* wait_for_tab_playing_event(tab, expectPlaying) {
+ if (tab.soundPlaying == expectPlaying) {
+ ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+ return true;
+ }
+ return yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
+ if (event.detail.changed.includes("soundplaying")) {
+ is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+ is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+ return true;
+ }
+ return false;
+ });
+}
+
+function* play(tab) {
+ let browser = tab.linkedBrowser;
+ yield ContentTask.spawn(browser, {}, function* () {
+ let audio = content.document.querySelector("audio");
+ audio.play();
+ });
+
+ yield wait_for_tab_playing_event(tab, true);
+}
+
+function* pause(tab, options) {
+ ok(tab.hasAttribute("soundplaying"), "The tab should have the soundplaying attribute when pause() is called");
+
+ let extendedDelay = options && options.extendedDelay;
+ if (extendedDelay) {
+ // Use 10s to remove possibility of race condition with attr removal.
+ Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, 10000);
+ }
+
+ try {
+ let browser = tab.linkedBrowser;
+ let awaitDOMAudioPlaybackStopped =
+ BrowserTestUtils.waitForEvent(browser, "DOMAudioPlaybackStopped", "DOMAudioPlaybackStopped event should get fired after pause");
+ let awaitTabPausedAttrModified =
+ wait_for_tab_playing_event(tab, false);
+ yield ContentTask.spawn(browser, {}, function* () {
+ let audio = content.document.querySelector("audio");
+ audio.pause();
+ });
+
+ if (extendedDelay) {
+ ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after pausing");
+
+ yield awaitDOMAudioPlaybackStopped;
+ ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped");
+ }
+
+ yield awaitTabPausedAttrModified;
+ ok(!tab.hasAttribute("soundplaying"), "The tab should not have the soundplaying attribute after the timeout has resolved");
+ } finally {
+ // Make sure other tests don't timeout if an exception gets thrown above.
+ // Need to use setIntPref instead of clearUserPref because prefs_general.js
+ // overrides the default value to help this and other tests run faster.
+ Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, INITIAL_TABATTR_REMOVAL_DELAY_MS);
+ }
+}
+
+function disable_non_test_mouse(disable) {
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.disableNonTestMouseEvents(disable);
+}
+
+function* hover_icon(icon, tooltip) {
+ disable_non_test_mouse(true);
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
+ EventUtils.synthesizeMouse(icon, 1, 1, {type: "mouseover"});
+ EventUtils.synthesizeMouse(icon, 2, 2, {type: "mousemove"});
+ EventUtils.synthesizeMouse(icon, 3, 3, {type: "mousemove"});
+ EventUtils.synthesizeMouse(icon, 4, 4, {type: "mousemove"});
+ return popupShownPromise;
+}
+
+function leave_icon(icon) {
+ EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+
+ disable_non_test_mouse(false);
+}
+
+function* test_tooltip(icon, expectedTooltip, isActiveTab) {
+ let tooltip = document.getElementById("tabbrowser-tab-tooltip");
+
+ yield hover_icon(icon, tooltip);
+ if (isActiveTab) {
+ // The active tab should have the keybinding shortcut in the tooltip.
+ // We check this by ensuring that the strings are not equal but the expected
+ // message appears in the beginning.
+ isnot(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
+ is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
+ } else {
+ is(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
+ }
+ leave_icon(icon);
+}
+
+// The set of tabs which have ever had their mute state changed.
+// Used to determine whether the tab should have a muteReason value.
+let everMutedTabs = new WeakSet();
+
+function get_wait_for_mute_promise(tab, expectMuted) {
+ return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => {
+ if (event.detail.changed.includes("muted")) {
+ is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted");
+ is(tab.muted, expectMuted, "The tab muted property " + (expectMuted ? "" : "not ") + "be true");
+
+ if (expectMuted || everMutedTabs.has(tab)) {
+ everMutedTabs.add(tab);
+ is(tab.muteReason, null, "The tab should have a null muteReason value");
+ } else {
+ is(tab.muteReason, undefined, "The tab should have an undefined muteReason value");
+ }
+ return true;
+ }
+ return false;
+ });
+}
+
+function* test_mute_tab(tab, icon, expectMuted) {
+ let mutedPromise = test_mute_keybinding(tab, expectMuted);
+
+ let activeTab = gBrowser.selectedTab;
+
+ let tooltip = document.getElementById("tabbrowser-tab-tooltip");
+
+ yield hover_icon(icon, tooltip);
+ EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
+ leave_icon(icon);
+
+ is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab");
+
+ return mutedPromise;
+}
+
+function get_tab_state(tab) {
+ const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ return JSON.parse(ss.getTabState(tab));
+}
+
+function* test_muting_using_menu(tab, expectMuted) {
+ // Show the popup menu
+ let contextMenu = document.getElementById("tabContextMenu");
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(tab, {type: "contextmenu", button: 2});
+ yield popupShownPromise;
+
+ // Check the menu
+ let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab";
+ let toggleMute = document.getElementById("context_toggleMuteTab");
+ is(toggleMute.label, expectedLabel, "Correct label expected");
+ is(toggleMute.accessKey, "M", "Correct accessKey expected");
+
+ is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+ ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
+
+ yield play(tab);
+
+ is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+ ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute");
+
+ yield pause(tab);
+
+ is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+ ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
+
+ // Click on the menu and wait for the tab to be muted.
+ let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted);
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ EventUtils.synthesizeMouseAtCenter(toggleMute, {});
+ yield popupHiddenPromise;
+ yield mutedPromise;
+}
+
+function* test_playing_icon_on_tab(tab, browser, isPinned) {
+ let icon = document.getAnonymousElementByAttribute(tab, "anonid",
+ isPinned ? "overlay-icon" : "soundplaying-icon");
+ let isActiveTab = tab === gBrowser.selectedTab;
+
+ yield play(tab);
+
+ yield test_tooltip(icon, "Mute tab", isActiveTab);
+
+ ok(!("muted" in get_tab_state(tab)), "No muted attribute should be persisted");
+ ok(!("muteReason" in get_tab_state(tab)), "No muteReason property should be persisted");
+
+ yield test_mute_tab(tab, icon, true);
+
+ ok("muted" in get_tab_state(tab), "Muted attribute should be persisted");
+ ok("muteReason" in get_tab_state(tab), "muteReason property should be persisted");
+
+ yield test_tooltip(icon, "Unmute tab", isActiveTab);
+
+ yield test_mute_tab(tab, icon, false);
+
+ ok(!("muted" in get_tab_state(tab)), "No muted attribute should be persisted");
+ ok(!("muteReason" in get_tab_state(tab)), "No muteReason property should be persisted");
+
+ yield test_tooltip(icon, "Mute tab", isActiveTab);
+
+ yield test_mute_tab(tab, icon, true);
+
+ yield pause(tab);
+
+ ok(tab.hasAttribute("muted") &&
+ !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing");
+ ok(tab.muted && !tab.soundPlaying, "Tab should still be muted but not playing");
+
+ yield test_tooltip(icon, "Unmute tab", isActiveTab);
+
+ yield test_mute_tab(tab, icon, false);
+
+ ok(!tab.hasAttribute("muted") &&
+ !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing");
+ ok(!tab.muted && !tab.soundPlaying, "Tab should not be be muted or playing");
+
+ // Make sure it's possible to mute using the context menu.
+ yield test_muting_using_menu(tab, false);
+
+ // Make sure it's possible to unmute using the context menu.
+ yield test_muting_using_menu(tab, true);
+}
+
+function* test_swapped_browser_while_playing(oldTab, newBrowser) {
+ ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
+ is(oldTab.muteReason, null, "Expected the correct muteReason attribute on the old tab");
+ ok(oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
+
+ let newTab = gBrowser.getTabForBrowser(newBrowser);
+ let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => {
+ return event.detail.changed.includes("soundplaying") &&
+ event.detail.changed.includes("muted");
+ });
+
+ gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
+ yield AttrChangePromise;
+
+ ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
+ is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
+ ok(newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+
+ let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
+ "soundplaying-icon");
+ yield test_tooltip(icon, "Unmute tab", true);
+}
+
+function* test_swapped_browser_while_not_playing(oldTab, newBrowser) {
+ ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
+ is(oldTab.muteReason, null, "Expected the correct muteReason property on the old tab");
+ ok(!oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
+
+ let newTab = gBrowser.getTabForBrowser(newBrowser);
+ let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => {
+ return event.detail.changed.includes("muted");
+ });
+
+ let AudioPlaybackPromise = new Promise(resolve => {
+ let observer = (subject, topic, data) => {
+ ok(false, "Should not see an audio-playback notification");
+ };
+ Services.obs.addObserver(observer, "audiochannel-activity-normal", false);
+ setTimeout(() => {
+ Services.obs.removeObserver(observer, "audiochannel-activity-normal");
+ resolve();
+ }, 100);
+ });
+
+ gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
+ yield AttrChangePromise;
+
+ ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
+ is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
+ ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+
+ // Wait to see if an audio-playback event is dispatched.
+ yield AudioPlaybackPromise;
+
+ ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
+ is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
+ ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+
+ let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
+ "soundplaying-icon");
+ yield test_tooltip(icon, "Unmute tab", true);
+}
+
+function* test_browser_swapping(tab, browser) {
+ // First, test swapping with a playing but muted tab.
+ yield play(tab);
+
+ let icon = document.getAnonymousElementByAttribute(tab, "anonid",
+ "soundplaying-icon");
+ yield test_mute_tab(tab, icon, true);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function*(newBrowser) {
+ yield test_swapped_browser_while_playing(tab, newBrowser)
+
+ // Now, test swapping with a muted but not playing tab.
+ // Note that the tab remains muted, so we only need to pause playback.
+ tab = gBrowser.getTabForBrowser(newBrowser);
+ yield pause(tab);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, secondAboutBlankBrowser => test_swapped_browser_while_not_playing(tab, secondAboutBlankBrowser));
+ });
+}
+
+function* test_click_on_pinned_tab_after_mute() {
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ gBrowser.selectedTab = originallySelectedTab;
+ isnot(tab, gBrowser.selectedTab, "Sanity check, the tab should not be selected!");
+
+ // Steps to reproduce the bug:
+ // Pin the tab.
+ gBrowser.pinTab(tab);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ // Mute the tab.
+ let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon");
+ yield test_mute_tab(tab, icon, true);
+
+ // Pause playback and wait for it to finish.
+ yield pause(tab);
+
+ // Unmute tab.
+ yield test_mute_tab(tab, icon, false);
+
+ // Now click on the tab.
+ let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
+ EventUtils.synthesizeMouseAtCenter(image, {button: 0});
+
+ is(tab, gBrowser.selectedTab, "Tab switch should be successful");
+
+ // Cleanup.
+ gBrowser.unpinTab(tab);
+ gBrowser.selectedTab = originallySelectedTab;
+ }
+
+ let originallySelectedTab = gBrowser.selectedTab;
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+// This test only does something useful in e10s!
+function* test_cross_process_load() {
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false,
+ event => event.detail.changed.includes("soundplaying")
+ );
+
+ // Go to a different process.
+ browser.loadURI("about:");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield soundPlayingStoppedPromise;
+
+ ok(!tab.hasAttribute("soundplaying"), "Tab should not be playing sound any more");
+ ok(!tab.soundPlaying, "Tab should not be playing sound any more");
+ }
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+function* test_mute_keybinding() {
+ function* test_muting_using_keyboard(tab) {
+ let mutedPromise = get_wait_for_mute_promise(tab, true);
+ EventUtils.synthesizeKey("m", {ctrlKey: true});
+ yield mutedPromise;
+ mutedPromise = get_wait_for_mute_promise(tab, false);
+ EventUtils.synthesizeKey("m", {ctrlKey: true});
+ yield mutedPromise;
+ }
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Make sure it's possible to mute before the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ // Make sure it's possible to mute after the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ // Pause playback and wait for it to finish.
+ yield pause(tab);
+
+ // Make sure things work if the tab is pinned.
+ gBrowser.pinTab(tab);
+
+ // Make sure it's possible to mute before the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ // Make sure it's possible to mute after the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ gBrowser.unpinTab(tab);
+ }
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+function* test_on_browser(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Test the icon in a normal tab.
+ yield test_playing_icon_on_tab(tab, browser, false);
+
+ gBrowser.pinTab(tab);
+
+ // Test the icon in a pinned tab.
+ yield test_playing_icon_on_tab(tab, browser, true);
+
+ gBrowser.unpinTab(tab);
+
+ // Retest with another browser in the foreground tab
+ if (gBrowser.selectedBrowser.currentURI.spec == PAGE) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "data:text/html,test"
+ }, () => test_on_browser(browser));
+ } else {
+ yield test_browser_swapping(tab, browser);
+ }
+}
+
+function* test_delayed_tabattr_removal() {
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield play(tab);
+
+ // Extend the delay to guarantee the soundplaying attribute
+ // is not removed from the tab when audio is stopped. Without
+ // the extended delay the attribute could be removed in the
+ // same tick and the test wouldn't catch that this broke.
+ yield pause(tab, {extendedDelay: true});
+ }
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+add_task(function*() {
+ yield new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["browser.tabs.showAudioPlayingIcon", true],
+ ]}, resolve);
+ });
+});
+
+requestLongerTimeout(2);
+add_task(function* test_page() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, test_on_browser);
+});
+
+add_task(test_click_on_pinned_tab_after_mute);
+
+add_task(test_cross_process_load);
+
+add_task(test_mute_keybinding);
+
+add_task(test_delayed_tabattr_removal);
diff --git a/browser/base/content/test/general/browser_backButtonFitts.js b/browser/base/content/test/general/browser_backButtonFitts.js
new file mode 100644
index 000000000..0e8aeeaee
--- /dev/null
+++ b/browser/base/content/test/general/browser_backButtonFitts.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* () {
+ let firstLocation = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, firstLocation);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ // Push the state before maximizing the window and clicking below.
+ content.history.pushState("page2", "page2", "page2");
+
+ // While in the child process, add a listener for the popstate event here. This
+ // event will fire when the mouse click happens.
+ content.addEventListener("popstate", function onPopState() {
+ content.removeEventListener("popstate", onPopState, false);
+ sendAsyncMessage("Test:PopStateOccurred", { location: content.document.location.href });
+ }, false);
+ });
+
+ window.maximize();
+
+ // Find where the nav-bar is vertically.
+ var navBar = document.getElementById("nav-bar");
+ var boundingRect = navBar.getBoundingClientRect();
+ var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
+ var xPixel = 0; // Use the first pixel of the screen since it is maximized.
+
+ let resultLocation = yield new Promise(resolve => {
+ messageManager.addMessageListener("Test:PopStateOccurred", function statePopped(message) {
+ messageManager.removeMessageListener("Test:PopStateOccurred", statePopped);
+ resolve(message.data.location);
+ });
+
+ EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
+ });
+
+ is(resultLocation, firstLocation, "Clicking the first pixel should have navigated back.");
+ window.restore();
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js
new file mode 100644
index 000000000..91a4a7e9c
--- /dev/null
+++ b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js
@@ -0,0 +1,76 @@
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html";
+
+var expectingDialog = false;
+var wantToClose = true;
+var resolveDialogPromise;
+function onTabModalDialogLoaded(node) {
+ ok(expectingDialog, "Should be expecting this dialog.");
+ expectingDialog = false;
+ if (wantToClose) {
+ // This accepts the dialog, closing it
+ node.Dialog.ui.button0.click();
+ } else {
+ // This keeps the page open
+ node.Dialog.ui.button1.click();
+ }
+ if (resolveDialogPromise) {
+ resolveDialogPromise();
+ }
+}
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+// Listen for the dialog being created
+Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.tabs.warnOnClose");
+ Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded");
+});
+
+add_task(function* closeLastTabInWindow() {
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let firstTab = newWin.gBrowser.selectedTab;
+ yield promiseTabLoadEvent(firstTab, TEST_PAGE);
+ let windowClosedPromise = promiseWindowWillBeClosed(newWin);
+ expectingDialog = true;
+ // close tab:
+ document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
+ yield windowClosedPromise;
+ ok(!expectingDialog, "There should have been a dialog.");
+ ok(newWin.closed, "Window should be closed.");
+});
+
+add_task(function* closeWindowWithMultipleTabsIncludingOneBeforeUnload() {
+ Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let firstTab = newWin.gBrowser.selectedTab;
+ yield promiseTabLoadEvent(firstTab, TEST_PAGE);
+ yield promiseTabLoadEvent(newWin.gBrowser.addTab(), "http://example.com/");
+ let windowClosedPromise = promiseWindowWillBeClosed(newWin);
+ expectingDialog = true;
+ newWin.BrowserTryToCloseWindow();
+ yield windowClosedPromise;
+ ok(!expectingDialog, "There should have been a dialog.");
+ ok(newWin.closed, "Window should be closed.");
+ Services.prefs.clearUserPref("browser.tabs.warnOnClose");
+});
+
+add_task(function* closeWindoWithSingleTabTwice() {
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let firstTab = newWin.gBrowser.selectedTab;
+ yield promiseTabLoadEvent(firstTab, TEST_PAGE);
+ let windowClosedPromise = promiseWindowWillBeClosed(newWin);
+ expectingDialog = true;
+ wantToClose = false;
+ let firstDialogShownPromise = new Promise((resolve, reject) => { resolveDialogPromise = resolve; });
+ document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
+ yield firstDialogShownPromise;
+ info("Got initial dialog, now trying again");
+ expectingDialog = true;
+ wantToClose = true;
+ resolveDialogPromise = null;
+ document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
+ yield windowClosedPromise;
+ ok(!expectingDialog, "There should have been a dialog.");
+ ok(newWin.closed, "Window should be closed.");
+});
diff --git a/browser/base/content/test/general/browser_blob-channelname.js b/browser/base/content/test/general/browser_blob-channelname.js
new file mode 100644
index 000000000..d87e4a896
--- /dev/null
+++ b/browser/base/content/test/general/browser_blob-channelname.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ var file = new File([new Blob(['test'], {type: 'text/plain'})], "test-name");
+ var url = URL.createObjectURL(file);
+ var channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+
+ is(channel.contentDispositionFilename, 'test-name', "filename matches");
+}
diff --git a/browser/base/content/test/general/browser_blockHPKP.js b/browser/base/content/test/general/browser_blockHPKP.js
new file mode 100644
index 000000000..c0d1233ab
--- /dev/null
+++ b/browser/base/content/test/general/browser_blockHPKP.js
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that visiting a site pinned with HPKP headers does not succeed when it
+// uses a certificate with a key not in the pinset. This should result in an
+// about:neterror page
+// Also verify that removal of the HPKP headers succeeds (via HPKP headers)
+// and that after removal the visit to the site with the previously
+// unauthorized pins succeeds.
+//
+// This test required three certs to be created in build/pgo/certs:
+// 1. A new trusted root:
+// a. certutil -S -s "Alternate trusted authority" -s "CN=Alternate Trusted Authority" -t "C,," -x -m 1 -v 120 -n "alternateTrustedAuthority" -Z SHA256 -g 2048 -2 -d .
+// b. (export) certutil -L -d . -n "alternateTrustedAuthority" -a -o alternateroot.ca
+// (files ended in .ca are added as trusted roots by the mochitest harness)
+// 2. A good pinning server cert (signed by the pgo root):
+// certutil -S -n "dynamicPinningGood" -s "CN=dynamic-pinning.example.com" -c "pgo temporary ca" -t "P,," -k rsa -g 2048 -Z SHA256 -m 8939454 -v 120 -8 "*.include-subdomains.pinning-dynamic.example.com,*.pinning-dynamic.example.com" -d .
+// 3. A certificate with a different issuer, so as to cause a key pinning violation."
+// certutil -S -n "dynamicPinningBad" -s "CN=bad.include-subdomains.pinning-dynamic.example.com" -c "alternateTrustedAuthority" -t "P,," -k rsa -g 2048 -Z SHA256 -m 893945439 -v 120 -8 "bad.include-subdomains.pinning-dynamic.example.com" -d .
+
+const gSSService = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+const gIOService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+const kPinningDomain = "include-subdomains.pinning-dynamic.example.com";
+const khpkpPinninEnablePref = "security.cert_pinning.process_headers_from_non_builtin_roots";
+const kpkpEnforcementPref = "security.cert_pinning.enforcement_level";
+const kBadPinningDomain = "bad.include-subdomains.pinning-dynamic.example.com";
+const kURLPath = "/browser/browser/base/content/test/general/pinning_headers.sjs?";
+
+function test() {
+ waitForExplicitFinish();
+ // Enable enforcing strict pinning and processing headers from
+ // non-builtin roots.
+ Services.prefs.setIntPref(kpkpEnforcementPref, 2);
+ Services.prefs.setBoolPref(khpkpPinninEnablePref, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(kpkpEnforcementPref);
+ Services.prefs.clearUserPref(khpkpPinninEnablePref);
+ let uri = gIOService.newURI("https://" + kPinningDomain, null, null);
+ gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
+ });
+ whenNewTabLoaded(window, loadPinningPage);
+}
+
+// Start by making a successful connection to a domain that will pin a site
+function loadPinningPage() {
+
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "valid").then(function() {
+ gBrowser.selectedBrowser.addEventListener("load",
+ successfulPinningPageListener,
+ true);
+ });
+}
+
+// After the site is pinned try to load with a subdomain site that should
+// fail to validate
+var successfulPinningPageListener = {
+ handleEvent: function() {
+ gBrowser.selectedBrowser.removeEventListener("load", this, true);
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() {
+ return promiseErrorPageLoaded(gBrowser.selectedBrowser);
+ }).then(errorPageLoaded);
+ }
+};
+
+// The browser should load about:neterror, when this happens, proceed
+// to load the pinning domain again, this time removing the pinning information
+function errorPageLoaded() {
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let textElement = content.document.getElementById("errorShortDescText");
+ let text = textElement.innerHTML;
+ ok(text.indexOf("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE") > 0,
+ "Got a pinning error page");
+ }).then(function() {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "zeromaxagevalid").then(function() {
+ return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ }).then(pinningRemovalLoaded);
+ });
+}
+
+// After the pinning information has been removed (successful load) proceed
+// to load again with the invalid pin domain.
+function pinningRemovalLoaded() {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() {
+ return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ }).then(badPinningPageLoaded);
+}
+
+// Finally, we should successfully load
+// https://bad.include-subdomains.pinning-dynamic.example.com.
+function badPinningPageLoaded() {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(function() {
+ ok(true, "load complete");
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_bookmark_popup.js b/browser/base/content/test/general/browser_bookmark_popup.js
new file mode 100644
index 000000000..c1ddd725e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bookmark_popup.js
@@ -0,0 +1,431 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Test opening and closing the bookmarks panel.
+ */
+
+let bookmarkPanel = document.getElementById("editBookmarkPanel");
+let bookmarkStar = document.getElementById("bookmarks-menu-button");
+let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
+let editBookmarkPanelRemoveButtonRect;
+
+StarUI._closePanelQuickForTesting = true;
+
+function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
+ shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
+ yield BrowserTestUtils.withNewTab({gBrowser, url: "about:home"}, function*(browser) {
+ try {
+ if (!isNewBookmark) {
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "about:home",
+ title: "Home Page"
+ });
+ }
+
+ info(`BookmarkingUI.status is ${BookmarkingUI.status}`);
+ yield BrowserTestUtils.waitForCondition(
+ () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING,
+ "BookmarkingUI should not be updating");
+
+ is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
+ "Page should only be starred prior to popupshown if editing bookmark");
+ is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
+ let shownPromise = promisePopupShown(bookmarkPanel);
+ yield popupShowFn(browser);
+ yield shownPromise;
+ is(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved");
+
+ editBookmarkPanelRemoveButtonRect =
+ document.getElementById("editBookmarkPanelRemoveButton").getBoundingClientRect();
+
+ if (popupEditFn) {
+ yield popupEditFn();
+ }
+ let bookmarks = [];
+ yield PlacesUtils.bookmarks.fetch({url: "about:home"}, bm => bookmarks.push(bm));
+ is(bookmarks.length, 1, "Only one bookmark should exist");
+ is(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
+ is(bookmarkPanelTitle.value,
+ isNewBookmark ?
+ gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
+ gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"),
+ "title should match isEditingBookmark state");
+
+ if (!shouldAutoClose) {
+ yield new Promise(resolve => setTimeout(resolve, 400));
+ is(bookmarkPanel.state, "open", "Panel should still be 'open' for non-autoclose");
+ }
+
+ let hiddenPromise = promisePopupHidden(bookmarkPanel);
+ if (popupHideFn) {
+ yield popupHideFn();
+ }
+ yield hiddenPromise;
+ is(bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved,
+ "Page is starred after closing");
+ } finally {
+ let bookmark = yield PlacesUtils.bookmarks.fetch({url: "about:home"});
+ is(!!bookmark, !isBookmarkRemoved,
+ "bookmark should not be present if a panel action should've removed it");
+ if (bookmark) {
+ yield PlacesUtils.bookmarks.remove(bookmark);
+ }
+ }
+ });
+}
+
+add_task(function* panel_shown_for_new_bookmarks_and_autocloses() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_once_for_doubleclick_on_new_bookmark_star_and_autocloses() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ EventUtils.synthesizeMouse(bookmarkStar, 10, 10, { clickCount: 2 },
+ window);
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() {
+ todo(false, "bug 1250267, may need to add some tracking state to " +
+ "browser-places.js for this.");
+ return;
+
+ /*
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ *popupShowFn() {
+ EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
+ yield new Promise(resolve => setTimeout(resolve, 300));
+ EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+ */
+});
+
+add_task(function* panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmarks_mousemove_mouseout() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ yield new Promise(resolve => setTimeout(resolve, 400));
+ is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");
+ },
+ *popupHideFn() {
+ let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
+ EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ info("Waiting for mouseout event");
+ yield mouseOutPromise;
+ info("Got mouseout event, should autoclose now");
+ },
+ shouldAutoClose: false,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_no_autoclose_close_with_ESC() {
+ yield test_bookmarks_popup({
+ isNewBookmark: false,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_editing_no_autoclose_close_with_ESC() {
+ yield test_bookmarks_popup({
+ isNewBookmark: false,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_keypress_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ popupEditFn() {
+ EventUtils.sendChar("VK_TAB", window);
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+
+add_task(function* panel_shown_for_new_bookmark_compositionstart_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart");
+ EventUtils.synthesizeComposition({ type: "compositionstart" }, window);
+ info("Waiting for compositionstart event");
+ yield compositionStartPromise;
+ info("Got compositionstart event");
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeComposition({ type: "compositioncommitasis" });
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_compositionstart_mouseout_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart");
+ EventUtils.synthesizeComposition({ type: "compositionstart" }, window);
+ info("Waiting for compositionstart event");
+ yield compositionStartPromise;
+ info("Got compositionstart event");
+
+ let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
+ EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ info("Waiting for mouseout event");
+ yield mouseOutPromise;
+ info("Got mouseout event, but shouldn't run autoclose");
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeComposition({ type: "compositioncommitasis" });
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_compositionend_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ EventUtils.synthesizeComposition({ type: "compositioncommit", data: "committed text" });
+ },
+ popupHideFn() {
+ bookmarkPanel.hidePopup();
+ },
+ shouldAutoClose: false,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* contextmenu_new_bookmark_keypress_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ *popupShowFn(browser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
+ "popupshown");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
+ "popuphidden");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
+ type: "contextmenu",
+ button: 2
+ }, browser);
+ yield awaitPopupShown;
+ document.getElementById("context-bookmarkpage").click();
+ contextMenu.hidePopup();
+ yield awaitPopupHidden;
+ },
+ popupEditFn() {
+ EventUtils.sendChar("VK_TAB", window);
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* bookmarks_menu_new_bookmark_remove_bookmark() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn(browser) {
+ document.getElementById("menu_bookmarkThisPage").doCommand();
+ },
+ shouldAutoClose: true,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* ctrl_d_edit_bookmark_remove_bookmark() {
+ yield test_bookmarks_popup({
+ isNewBookmark: false,
+ popupShowFn(browser) {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: true,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* enter_on_remove_bookmark_should_remove_bookmark() {
+ if (AppConstants.platform == "macosx") {
+ // "Full Keyboard Access" is disabled by default, and thus doesn't allow
+ // keyboard navigation to the "Remove Bookmarks" button by default.
+ return;
+ }
+
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn(browser) {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: true,
+ popupHideFn() {
+ while (!document.activeElement ||
+ document.activeElement.id != "editBookmarkPanelRemoveButton") {
+ EventUtils.sendChar("VK_TAB", window);
+ }
+ EventUtils.sendChar("VK_RETURN", window);
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* ctrl_d_new_bookmark_mousedown_mouseout_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn(browser) {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ yield new Promise(resolve => setTimeout(resolve, 400));
+ is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");
+
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanelTitle, {button: 1, type: "mousedown"});
+
+ let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
+ EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ info("Waiting for mouseout event");
+ yield mouseOutPromise;
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* mouse_hovering_panel_should_prevent_autoclose() {
+ if (AppConstants.platform != "win") {
+ // This test requires synthesizing native mouse movement which is
+ // best supported on Windows.
+ return;
+ }
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ *popupShowFn(browser) {
+ yield new Promise(resolve => {
+ EventUtils.synthesizeNativeMouseMove(
+ document.documentElement,
+ editBookmarkPanelRemoveButtonRect.left,
+ editBookmarkPanelRemoveButtonRect.top,
+ resolve);
+ });
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+registerCleanupFunction(function() {
+ delete StarUI._closePanelQuickForTesting;
+});
diff --git a/browser/base/content/test/general/browser_bookmark_titles.js b/browser/base/content/test/general/browser_bookmark_titles.js
new file mode 100644
index 000000000..1f7082396
--- /dev/null
+++ b/browser/base/content/test/general/browser_bookmark_titles.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is tests for the default titles that new bookmarks get.
+
+var tests = [
+ // Common page.
+ ['http://example.com/browser/browser/base/content/test/general/dummy_page.html',
+ 'Dummy test page'],
+ // Data URI.
+ ['data:text/html;charset=utf-8,<title>test%20data:%20url</title>',
+ 'test data: url'],
+ // about:neterror
+ ['data:application/vnd.mozilla.xul+xml,',
+ 'data:application/vnd.mozilla.xul+xml,'],
+ // about:certerror
+ ['https://untrusted.example.com/somepage.html',
+ 'https://untrusted.example.com/somepage.html']
+];
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ let browser = gBrowser.selectedBrowser;
+ browser.stop(); // stop the about:blank load.
+
+ // Test that a bookmark of each URI gets the corresponding default title.
+ for (let i = 0; i < tests.length; ++i) {
+ let [uri, title] = tests[i];
+
+ let promiseLoaded = promisePageLoaded(browser);
+ BrowserTestUtils.loadURI(browser, uri);
+ yield promiseLoaded;
+ yield checkBookmark(uri, title);
+ }
+
+ // Network failure test: now that dummy_page.html is in history, bookmarking
+ // it should give the last known page title as the default bookmark title.
+
+ // Simulate a network outage with offline mode. (Localhost is still
+ // accessible in offline mode, so disable the test proxy as well.)
+ BrowserOffline.toggleOfflineStatus();
+ let proxy = Services.prefs.getIntPref('network.proxy.type');
+ Services.prefs.setIntPref('network.proxy.type', 0);
+ registerCleanupFunction(function () {
+ BrowserOffline.toggleOfflineStatus();
+ Services.prefs.setIntPref('network.proxy.type', proxy);
+ });
+
+ // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+ Services.cache2.clear();
+
+ let [uri, title] = tests[0];
+
+ let promiseLoaded = promisePageLoaded(browser);
+ BrowserTestUtils.loadURI(browser, uri);
+ yield promiseLoaded;
+
+ // The offline mode test is only good if the page failed to load.
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.document.documentURI.substring(0, 14), 'about:neterror',
+ "Offline mode successfully simulated network outage.");
+ });
+ yield checkBookmark(uri, title);
+
+ gBrowser.removeCurrentTab();
+});
+
+// Bookmark the current page and confirm that the new bookmark has the expected
+// title. (Then delete the bookmark.)
+function* checkBookmark(uri, expected_title) {
+ is(gBrowser.selectedBrowser.currentURI.spec, uri,
+ "Trying to bookmark the expected uri");
+
+ let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.selectedBrowser.currentURI);
+ PlacesCommandHook.bookmarkCurrentPage(false);
+ yield promiseBookmark;
+
+ let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri));
+ ok(id > 0, "Found the expected bookmark");
+ let title = PlacesUtils.bookmarks.getItemTitle(id);
+ is(title, expected_title, "Bookmark got a good default title.");
+
+ PlacesUtils.bookmarks.removeItem(id);
+}
+
+// BrowserTestUtils.browserLoaded doesn't work for the about pages, so use a
+// custom page load listener.
+function promisePageLoaded(browser)
+{
+ return ContentTask.spawn(browser, null, function* () {
+ yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true,
+ (event) => {
+ return event.originalTarget === content.document &&
+ event.target.location.href !== "about:blank"
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug1015721.js b/browser/base/content/test/general/browser_bug1015721.js
new file mode 100644
index 000000000..e3e715396
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1015721.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html";
+
+var gTab1, gTab2, gLevel1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTab1 = gBrowser.addTab();
+ gTab2 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, TEST_PAGE);
+ yield FullZoomHelper.load(gTab2, TEST_PAGE);
+ }).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab1() {
+ Task.spawn(function* () {
+ is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+
+ let browser1 = gBrowser.getBrowserForTab(gTab1);
+ yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {
+ wheel: true, ctrlKey: true, deltaY: -1, deltaMode: WheelEvent.DOM_DELTA_LINE
+ }, browser1);
+
+ info("Waiting for tab 1 to be zoomed");
+ yield promiseWaitForCondition(() => {
+ gLevel1 = ZoomManager.getZoomForBrowser(browser1);
+ return gLevel1 > 1;
+ });
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab2, gLevel1, "Tab 2 should have zoomed along with tab 1");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+function finishTest() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug1045809.js b/browser/base/content/test/general/browser_bug1045809.js
new file mode 100644
index 000000000..63b6b06d5
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1045809.js
@@ -0,0 +1,68 @@
+// Test that the Mixed Content Doorhanger Action to re-enable protection works
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+var origBlockActive;
+
+add_task(function* () {
+ registerCleanupFunction(function() {
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+ gBrowser.removeCurrentTab();
+ });
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ // Make sure mixed content blocking is on
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ var url =
+ "https://test1.example.com/browser/browser/base/content/test/general/" +
+ "file_bug1045809_1.html";
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ // Test 1: mixed content must be blocked
+ yield promiseTabLoadEvent(tab, url);
+ yield* test1(gBrowser.getBrowserForTab(tab));
+
+ yield promiseTabLoadEvent(tab);
+ // Test 2: mixed content must NOT be blocked
+ yield* test2(gBrowser.getBrowserForTab(tab));
+
+ // Test 3: mixed content must be blocked again
+ yield promiseTabLoadEvent(tab);
+ yield* test3(gBrowser.getBrowserForTab(tab));
+});
+
+function* test1(gTestBrowser) {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer");
+ is(x, null, "Mixed Content is NOT to be found in Test1");
+ });
+
+ // Disable Mixed Content Protection for the page (and reload)
+ gIdentityHandler.disableMixedContentProtection();
+}
+
+function* test2(gTestBrowser) {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer");
+ isnot(x, null, "Mixed Content is to be found in Test2");
+ });
+
+ // Re-enable Mixed Content Protection for the page (and reload)
+ gIdentityHandler.enableMixedContentProtection();
+}
+
+function* test3(gTestBrowser) {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer");
+ is(x, null, "Mixed Content is NOT to be found in Test3");
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js
new file mode 100644
index 000000000..98e0e74db
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+ // Test that changing the URL in a pinned tab works correctly
+
+ let TEST_LINK_INITIAL = "about:";
+ let TEST_LINK_CHANGED = "about:support";
+
+ let appTab = gBrowser.addTab(TEST_LINK_INITIAL);
+ let browser = appTab.linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ gBrowser.pinTab(appTab);
+ is(appTab.pinned, true, "Tab was successfully pinned");
+
+ let initialTabsNo = gBrowser.tabs.length;
+
+ let goButton = document.getElementById("urlbar-go-button");
+ gBrowser.selectedTab = appTab;
+ gURLBar.focus();
+ gURLBar.value = TEST_LINK_CHANGED;
+
+ goButton.click();
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ is(appTab.linkedBrowser.currentURI.spec, TEST_LINK_CHANGED,
+ "New page loaded in the app tab");
+ is(gBrowser.tabs.length, initialTabsNo, "No additional tabs were opened");
+});
+
+registerCleanupFunction(function () {
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_bug1261299.js b/browser/base/content/test/general/browser_bug1261299.js
new file mode 100644
index 000000000..673ef2a0a
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1261299.js
@@ -0,0 +1,73 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests for Bug 1261299
+ * Test that the service menu code path is called properly and the
+ * current selection (transferable) is cached properly on the parent process.
+ */
+
+add_task(function* test_content_and_chrome_selection()
+{
+ let testPage =
+ 'data:text/html,' +
+ '<textarea id="textarea">Write something here</textarea>';
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ let selectedText;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "Write something here", "The macOS services got the selected content text");
+
+ gURLBar.value = "test.mozilla.org";
+ yield gURLBar.focus();
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "test.mozilla.org", "The macOS services got the selected chrome text");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// Test switching active selection.
+// Each tab has a content selection and when you switch to that tab, its selection becomes
+// active aka the current selection.
+// Expect: The active selection is what is being sent to OSX service menu.
+
+add_task(function* test_active_selection_switches_properly()
+{
+ let testPage1 =
+ 'data:text/html,' +
+ '<textarea id="textarea">Write something here</textarea>';
+ let testPage2 =
+ 'data:text/html,' +
+ '<textarea id="textarea">Nothing available</textarea>';
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ let selectedText;
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1);
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2);
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "Write something here", "The macOS services got the selected content text");
+
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "Nothing available", "The macOS services got the selected content text");
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/base/content/test/general/browser_bug1297539.js b/browser/base/content/test/general/browser_bug1297539.js
new file mode 100644
index 000000000..d7e675437
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1297539.js
@@ -0,0 +1,114 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for Bug 1297539
+ * Test that the content event "pasteTransferable"
+ * (mozilla::EventMessage::eContentCommandPasteTransferable)
+ * is handled correctly for plain text and html in the remote case.
+ *
+ * Original test test_bug525389.html for command content event
+ * "pasteTransferable" runs only in the content process.
+ * This doesn't test the remote case.
+ *
+ */
+
+"use strict";
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function getTransferableFromClipboard(asHTML) {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ if (asHTML) {
+ trans.addDataFlavor("text/html");
+ } else {
+ trans.addDataFlavor("text/unicode");
+ }
+ let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+ clip.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
+ return trans;
+}
+
+function* cutCurrentSelection(elementQueryString, property, browser) {
+ // Cut the current selection.
+ yield BrowserTestUtils.synthesizeKey("x", {accelKey: true}, browser);
+
+ // The editor should be empty after cut.
+ yield ContentTask.spawn(browser, [elementQueryString, property],
+ function* ([contentElementQueryString, contentProperty]) {
+ let element = content.document.querySelector(contentElementQueryString);
+ is(element[contentProperty], "",
+ `${contentElementQueryString} should be empty after cut (superkey + x)`);
+ });
+}
+
+// Test that you are able to pasteTransferable for plain text
+// which is handled by TextEditor::PasteTransferable to paste into the editor.
+add_task(function* test_paste_transferable_plain_text()
+{
+ let testPage =
+ 'data:text/html,' +
+ '<textarea id="textarea">Write something here</textarea>';
+
+ yield BrowserTestUtils.withNewTab(testPage, function* (browser) {
+ // Select all the content in your editor element.
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, browser);
+ yield BrowserTestUtils.synthesizeKey("a", {accelKey: true}, browser);
+
+ yield* cutCurrentSelection("#textarea", "value", browser);
+
+ let trans = getTransferableFromClipboard(false);
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let textArea = content.document.querySelector('#textarea');
+ is(textArea.value, "Write something here",
+ "Send content command pasteTransferable successful");
+ });
+ });
+});
+
+// Test that you are able to pasteTransferable for html
+// which is handled by HTMLEditor::PasteTransferable to paste into the editor.
+//
+// On Linux,
+// BrowserTestUtils.synthesizeKey("a", {accelKey: true}, browser);
+// doesn't seem to trigger for contenteditable which is why we use
+// Selection to select the contenteditable contents.
+add_task(function* test_paste_transferable_html()
+{
+ let testPage =
+ 'data:text/html,' +
+ '<div contenteditable="true"><b>Bold Text</b><i>italics</i></div>';
+
+ yield BrowserTestUtils.withNewTab(testPage, function* (browser) {
+ // Select all the content in your editor element.
+ yield BrowserTestUtils.synthesizeMouse("div", 0, 0, {}, browser);
+ yield ContentTask.spawn(browser, {}, function* () {
+ let element = content.document.querySelector("div");
+ let selection = content.window.getSelection();
+ selection.selectAllChildren(element);
+ });
+
+ yield* cutCurrentSelection("div", "textContent", browser);
+
+ let trans = getTransferableFromClipboard(true);
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let textArea = content.document.querySelector('div');
+ is(textArea.innerHTML, "<b>Bold Text</b><i>italics</i>",
+ "Send content command pasteTransferable successful");
+ });
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug1299667.js b/browser/base/content/test/general/browser_bug1299667.js
new file mode 100644
index 000000000..084c8d49f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1299667.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { addObserver, removeObserver } = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+
+function receive(topic) {
+ return new Promise((resolve, reject) => {
+ let timeout = setTimeout(() => {
+ reject(new Error("Timeout"));
+ }, 90000);
+
+ const observer = {
+ observe: subject => {
+ removeObserver(observer, topic);
+ clearTimeout(timeout);
+ resolve(subject);
+ }
+ };
+ addObserver(observer, topic, false);
+ });
+}
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.history.pushState({}, "2", "2.html");
+ });
+
+ yield receive("sessionstore-state-write-complete");
+
+ // Wait for the session data to be flushed before continuing the test
+ yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
+
+ let backButton = document.getElementById("back-button");
+ let contextMenu = document.getElementById("backForwardMenu");
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(backButton, {type: "contextmenu", button: 2});
+ let event = yield popupShownPromise;
+
+ ok(true, "history menu opened");
+
+ // Wait for the session data to be flushed before continuing the test
+ yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
+
+ is(event.target.children.length, 2, "Two history items");
+
+ let node = event.target.firstChild;
+ is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri");
+ is(node.getAttribute("index"), "1", "first item index");
+ is(node.getAttribute("historyindex"), "0", "first item historyindex");
+
+ node = event.target.lastChild;
+ is(node.getAttribute("uri"), "http://example.com/", "second item uri");
+ is(node.getAttribute("index"), "0", "second item index");
+ is(node.getAttribute("historyindex"), "-1", "second item historyindex");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ event.target.hidePopup();
+ yield popupHiddenPromise;
+ info("Hidden popup");
+
+ let onClose = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield onClose;
+ info("Tab closed");
+});
diff --git a/browser/base/content/test/general/browser_bug321000.js b/browser/base/content/test/general/browser_bug321000.js
new file mode 100644
index 000000000..b30b7101d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug321000.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const kTestString = " hello hello \n world\nworld ";
+
+var gTests = [
+
+ { desc: "Urlbar strips newlines and surrounding whitespace",
+ element: gURLBar,
+ expected: kTestString.replace(/\s*\n\s*/g, '')
+ },
+
+ { desc: "Searchbar replaces newlines with spaces",
+ element: document.getElementById('searchbar'),
+ expected: kTestString.replace(/\n/g, ' ')
+ },
+
+];
+
+// Test for bug 23485 and bug 321000.
+// Urlbar should strip newlines,
+// search bar should replace newlines with spaces.
+function test() {
+ waitForExplicitFinish();
+
+ let cbHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+ // Put a multi-line string in the clipboard.
+ // Setting the clipboard value is an async OS operation, so we need to poll
+ // the clipboard for valid data before going on.
+ waitForClipboard(kTestString, function() { cbHelper.copyString(kTestString); },
+ next_test, finish);
+}
+
+function next_test() {
+ if (gTests.length)
+ test_paste(gTests.shift());
+ else
+ finish();
+}
+
+function test_paste(aCurrentTest) {
+ var element = aCurrentTest.element;
+
+ // Register input listener.
+ var inputListener = {
+ test: aCurrentTest,
+ handleEvent: function(event) {
+ element.removeEventListener(event.type, this, false);
+
+ is(element.value, this.test.expected, this.test.desc);
+
+ // Clear the field and go to next test.
+ element.value = "";
+ setTimeout(next_test, 0);
+ }
+ }
+ element.addEventListener("input", inputListener, false);
+
+ // Focus the window.
+ window.focus();
+ gBrowser.selectedBrowser.focus();
+
+ // Focus the element and wait for focus event.
+ info("About to focus " + element.id);
+ element.addEventListener("focus", function() {
+ element.removeEventListener("focus", arguments.callee, false);
+ executeSoon(function() {
+ // Pasting is async because the Accel+V codepath ends up going through
+ // nsDocumentViewer::FireClipboardEvent.
+ info("Pasting into " + element.id);
+ EventUtils.synthesizeKey("v", { accelKey: true });
+ });
+ }, false);
+ element.focus();
+}
diff --git a/browser/base/content/test/general/browser_bug356571.js b/browser/base/content/test/general/browser_bug356571.js
new file mode 100644
index 000000000..ab689d0f8
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug356571.js
@@ -0,0 +1,93 @@
+// Bug 356571 - loadOneOrMoreURIs gives up if one of the URLs has an unknown protocol
+
+var Cr = Components.results;
+var Cm = Components.manager;
+
+// Set to true when docShell alerts for unknown protocol error
+var didFail = false;
+
+// Override Alert to avoid blocking the test due to unknown protocol error
+const kPromptServiceUUID = "{6cc9c9fe-bc0b-432b-a410-253ef8bcc699}";
+const kPromptServiceContractID = "@mozilla.org/embedcomp/prompt-service;1";
+
+// Save original prompt service factory
+const kPromptServiceFactory = Cm.getClassObject(Cc[kPromptServiceContractID],
+ Ci.nsIFactory);
+
+var fakePromptServiceFactory = {
+ createInstance: function(aOuter, aIid) {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return promptService.QueryInterface(aIid);
+ }
+};
+
+var promptService = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]),
+ alert: function() {
+ didFail = true;
+ }
+};
+
+/* FIXME
+Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service",
+ kPromptServiceContractID, fakePromptServiceFactory);
+*/
+
+const kCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+const kDummyPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+const kURIs = [
+ "bad://www.mozilla.org/",
+ kDummyPage,
+ kDummyPage,
+];
+
+var gProgressListener = {
+ _runCount: 0,
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ if ((aStateFlags & kCompleteState) == kCompleteState) {
+ if (++this._runCount != kURIs.length)
+ return;
+ // Check we failed on unknown protocol (received an alert from docShell)
+ ok(didFail, "Correctly failed on unknown protocol");
+ // Check we opened all tabs
+ ok(gBrowser.tabs.length == kURIs.length, "Correctly opened all expected tabs");
+ finishTest();
+ }
+ }
+}
+
+function test() {
+ todo(false, "temp. disabled");
+ return; /* FIXME */
+ /*
+ waitForExplicitFinish();
+ // Wait for all tabs to finish loading
+ gBrowser.addTabsProgressListener(gProgressListener);
+ loadOneOrMoreURIs(kURIs.join("|"));
+ */
+}
+
+function finishTest() {
+ // Unregister the factory so we do not leak
+ Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .unregisterFactory(Components.ID(kPromptServiceUUID),
+ fakePromptServiceFactory);
+
+ // Restore the original factory
+ Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service",
+ kPromptServiceContractID, kPromptServiceFactory);
+
+ // Remove the listener
+ gBrowser.removeTabsProgressListener(gProgressListener);
+
+ // Close opened tabs
+ for (var i = gBrowser.tabs.length-1; i > 0; i--)
+ gBrowser.removeTab(gBrowser.tabs[i]);
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug380960.js b/browser/base/content/test/general/browser_bug380960.js
new file mode 100644
index 000000000..d6b64543b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug380960.js
@@ -0,0 +1,11 @@
+function test() {
+ var tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ gBrowser.removeTab(tab);
+ is(tab.parentNode, null, "tab removed immediately");
+
+ tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ gBrowser.removeTab(tab, { animate: true });
+ gBrowser.removeTab(tab);
+ is(tab.parentNode, null, "tab removed immediately when calling removeTab again after the animation was kicked off");
+}
+
diff --git a/browser/base/content/test/general/browser_bug386835.js b/browser/base/content/test/general/browser_bug386835.js
new file mode 100644
index 000000000..1c3ba99c5
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug386835.js
@@ -0,0 +1,89 @@
+var gTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+var gTestImage = "http://example.org/browser/browser/base/content/test/general/moz.png";
+var gTab1, gTab2, gTab3;
+var gLevel;
+const BACK = 0;
+const FORWARD = 1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTab1 = gBrowser.addTab(gTestPage);
+ gTab2 = gBrowser.addTab();
+ gTab3 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, gTestPage);
+ yield FullZoomHelper.load(gTab2, gTestPage);
+ }).then(secondPageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function secondPageLoaded() {
+ Task.spawn(function* () {
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+ FullZoomHelper.zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1");
+
+ // Now have three tabs, two with the test page, one blank. Tab 1 is selected
+ // Zoom tab 1
+ FullZoom.enlarge();
+ gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+
+ ok(gLevel > 1, "New zoom for tab 1 should be greater than 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
+ FullZoomHelper.zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3");
+
+ yield FullZoomHelper.load(gTab3, gTestPage);
+ }).then(thirdPageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function thirdPageLoaded() {
+ Task.spawn(function* () {
+ FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
+ FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 should still not be affected");
+ FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should have zoomed as it was loading in the background");
+
+ // Switching to tab 2 should update its zoom setting.
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
+ FullZoomHelper.zoomTest(gTab2, gLevel, "Tab 2 should be zoomed now");
+ FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should still be zoomed");
+
+ yield FullZoomHelper.load(gTab1, gTestImage);
+ }).then(imageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function imageLoaded() {
+ Task.spawn(function* () {
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when image was loaded in the background");
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should still be 1 when tab with image is selected");
+ }).then(imageZoomSwitch, FullZoomHelper.failAndContinue(finish));
+}
+
+function imageZoomSwitch() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.navigate(BACK);
+ yield FullZoomHelper.navigate(FORWARD);
+ FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should not be zoomed when an image loads");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should still not be zoomed when deselected");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+var finishTestStarted = false;
+function finishTest() {
+ Task.spawn(function* () {
+ ok(!finishTestStarted, "finishTest called more than once");
+ finishTestStarted = true;
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab3);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug406216.js b/browser/base/content/test/general/browser_bug406216.js
new file mode 100644
index 000000000..e1bd38395
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug406216.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * "TabClose" event is possibly used for closing related tabs of the current.
+ * "removeTab" method should work correctly even if the number of tabs are
+ * changed while "TabClose" event.
+ */
+
+var count = 0;
+const URIS = ["about:config",
+ "about:plugins",
+ "about:buildconfig",
+ "data:text/html,<title>OK</title>"];
+
+function test() {
+ waitForExplicitFinish();
+ URIS.forEach(addTab);
+}
+
+function addTab(aURI, aIndex) {
+ var tab = gBrowser.addTab(aURI);
+ if (aIndex == 0)
+ gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
+
+ tab.linkedBrowser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ if (++count == URIS.length)
+ executeSoon(doTabsTest);
+ }, true);
+}
+
+function doTabsTest() {
+ is(gBrowser.tabs.length, URIS.length, "Correctly opened all expected tabs");
+
+ // sample of "close related tabs" feature
+ gBrowser.tabContainer.addEventListener("TabClose", function (event) {
+ event.currentTarget.removeEventListener("TabClose", arguments.callee, true);
+ var closedTab = event.originalTarget;
+ var scheme = closedTab.linkedBrowser.currentURI.scheme;
+ Array.slice(gBrowser.tabs).forEach(function (aTab) {
+ if (aTab != closedTab && aTab.linkedBrowser.currentURI.scheme == scheme)
+ gBrowser.removeTab(aTab, {skipPermitUnload: true});
+ });
+ }, true);
+
+ gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
+ is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly");
+
+ gBrowser.addTab("about:blank");
+ gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug408415.js b/browser/base/content/test/general/browser_bug408415.js
new file mode 100644
index 000000000..d8f80f8be
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug408415.js
@@ -0,0 +1,45 @@
+add_task(function* test() {
+ let testPath = getRootDirectory(gTestPath);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (tabBrowser) {
+ const URI = testPath + "file_with_favicon.html";
+ const expectedIcon = testPath + "file_generic_favicon.ico";
+
+ let got_favicon = Promise.defer();
+ let listener = {
+ onLinkIconAvailable(browser, iconURI) {
+ if (got_favicon && iconURI && browser === tabBrowser) {
+ got_favicon.resolve(iconURI);
+ got_favicon = null;
+ }
+ }
+ };
+ gBrowser.addTabsProgressListener(listener);
+
+ BrowserTestUtils.loadURI(tabBrowser, URI);
+
+ let iconURI = yield got_favicon.promise;
+ is(iconURI, expectedIcon, "Correct icon before pushState.");
+
+ got_favicon = Promise.defer();
+ got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e);
+ yield ContentTask.spawn(tabBrowser, null, function() {
+ content.location.href += "#foo";
+ });
+
+ // We've navigated and shouldn't get a call to onLinkIconAvailable.
+ TestUtils.executeSoon(() => {
+ got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser)));
+ });
+ try {
+ yield got_favicon.promise;
+ } catch (e) {
+ iconURI = e;
+ }
+ is(iconURI, expectedIcon, "Correct icon after pushState.");
+
+ gBrowser.removeTabsProgressListener(listener);
+ });
+});
+
diff --git a/browser/base/content/test/general/browser_bug409481.js b/browser/base/content/test/general/browser_bug409481.js
new file mode 100644
index 000000000..395ad93d4
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug409481.js
@@ -0,0 +1,83 @@
+function test() {
+ waitForExplicitFinish();
+
+ // XXX This looks a bit odd, but is needed to avoid throwing when removing the
+ // event listeners below. See bug 310955.
+ document.getElementById("sidebar").addEventListener("load", delayedOpenUrl, true);
+ SidebarUI.show("viewWebPanelsSidebar");
+}
+
+function delayedOpenUrl() {
+ ok(true, "Ran delayedOpenUrl");
+ setTimeout(openPanelUrl, 100);
+}
+
+function openPanelUrl(event) {
+ ok(!document.getElementById("sidebar-box").hidden, "Sidebar showing");
+
+ var sidebar = document.getElementById("sidebar");
+ var root = sidebar.contentDocument.documentElement;
+ ok(root.nodeName != "parsererror", "Sidebar is well formed");
+
+ sidebar.removeEventListener("load", delayedOpenUrl, true);
+ // XXX See comment above
+ sidebar.contentDocument.addEventListener("load", delayedRunTest, true);
+ var url = 'data:text/html,<div%20id="test_bug409481">Content!</div><a id="link" href="http://www.example.com/ctest">Link</a><input id="textbox">';
+ sidebar.contentWindow.loadWebPanel(url);
+}
+
+function delayedRunTest() {
+ ok(true, "Ran delayedRunTest");
+ setTimeout(runTest, 100);
+}
+
+function runTest(event) {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("load", delayedRunTest, true);
+
+ var browser = sidebar.contentDocument.getElementById("web-panels-browser");
+ var div = browser && browser.contentDocument.getElementById("test_bug409481");
+ ok(div && div.textContent == "Content!", "Sidebar content loaded");
+
+ var link = browser && browser.contentDocument.getElementById("link");
+ sidebar.contentDocument.addEventListener("popupshown", contextMenuOpened, false);
+
+ EventUtils.synthesizeMouseAtCenter(link, { type: "contextmenu", button: 2 }, browser.contentWindow);
+}
+
+function contextMenuOpened()
+{
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("popupshown", contextMenuOpened, false);
+
+ var copyLinkCommand = sidebar.contentDocument.getElementById("context-copylink");
+ copyLinkCommand.addEventListener("command", copyLinkCommandExecuted, false);
+ copyLinkCommand.doCommand();
+}
+
+function copyLinkCommandExecuted(event)
+{
+ event.target.removeEventListener("command", copyLinkCommandExecuted, false);
+
+ var sidebar = document.getElementById("sidebar");
+ var browser = sidebar.contentDocument.getElementById("web-panels-browser");
+ var textbox = browser && browser.contentDocument.getElementById("textbox");
+ textbox.focus();
+ document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ is(textbox.value, "http://www.example.com/ctest", "copy link command");
+
+ sidebar.contentDocument.addEventListener("popuphidden", contextMenuClosed, false);
+ event.target.parentNode.hidePopup();
+}
+
+function contextMenuClosed()
+{
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("popuphidden", contextMenuClosed, false);
+
+ SidebarUI.hide();
+
+ ok(document.getElementById("sidebar-box").hidden, "Sidebar successfully hidden");
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug409624.js b/browser/base/content/test/general/browser_bug409624.js
new file mode 100644
index 000000000..8e46ec0c2
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug409624.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+add_task(function* test() {
+ // This test relies on the form history being empty to start with delete
+ // all the items first.
+ yield new Promise((resolve, reject) => {
+ FormHistory.update({ op: "remove" },
+ { handleError(error) {
+ reject(error);
+ },
+ handleCompletion(reason) {
+ if (!reason) {
+ resolve();
+ } else {
+ reject();
+ }
+ },
+ });
+ });
+
+ let prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+
+ let tempScope = {};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+ let Sanitizer = tempScope.Sanitizer;
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+ let prefBranch = prefService.getBranch(s.prefDomain);
+
+ prefBranch.setBoolPref("cache", false);
+ prefBranch.setBoolPref("cookies", false);
+ prefBranch.setBoolPref("downloads", false);
+ prefBranch.setBoolPref("formdata", true);
+ prefBranch.setBoolPref("history", false);
+ prefBranch.setBoolPref("offlineApps", false);
+ prefBranch.setBoolPref("passwords", false);
+ prefBranch.setBoolPref("sessions", false);
+ prefBranch.setBoolPref("siteSettings", false);
+
+ // Sanitize now so we can test the baseline point.
+ yield s.sanitize();
+ ok(!gFindBar.hasTransactions, "pre-test baseline for sanitizer");
+
+ gFindBar.getElement("findbar-textbox").value = "m";
+ ok(gFindBar.hasTransactions, "formdata can be cleared after input");
+
+ yield s.sanitize();
+ is(gFindBar.getElement("findbar-textbox").value, "", "findBar textbox should be empty after sanitize");
+ ok(!gFindBar.hasTransactions, "No transactions after sanitize");
+});
diff --git a/browser/base/content/test/general/browser_bug413915.js b/browser/base/content/test/general/browser_bug413915.js
new file mode 100644
index 000000000..86c94c427
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug413915.js
@@ -0,0 +1,62 @@
+XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+
+function test() {
+ var exampleUri = makeURI("http://example.com/");
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ var principal = secman.createCodebasePrincipal(exampleUri, {});
+
+ function testIsFeed(aTitle, aHref, aType, aKnown) {
+ var link = { title: aTitle, href: aHref, type: aType };
+ return Feeds.isValidFeed(link, principal, aKnown);
+ }
+
+ var href = "http://example.com/feed/";
+ var atomType = "application/atom+xml";
+ var funkyAtomType = " aPPLICAtion/Atom+XML ";
+ var rssType = "application/rss+xml";
+ var funkyRssType = " Application/RSS+XML ";
+ var rdfType = "application/rdf+xml";
+ var texmlType = "text/xml";
+ var appxmlType = "application/xml";
+ var noRss = "Foo";
+ var rss = "RSS";
+
+ // things that should be valid
+ ok(testIsFeed(noRss, href, atomType, false) == atomType,
+ "detect Atom feed");
+ ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType,
+ "clean up and detect Atom feed");
+ ok(testIsFeed(noRss, href, rssType, false) == rssType,
+ "detect RSS feed");
+ ok(testIsFeed(noRss, href, funkyRssType, false) == rssType,
+ "clean up and detect RSS feed");
+
+ // things that should not be feeds
+ ok(testIsFeed(noRss, href, rdfType, false) == null,
+ "should not detect RDF non-feed");
+ ok(testIsFeed(rss, href, rdfType, false) == null,
+ "should not detect RDF feed from type and title");
+ ok(testIsFeed(noRss, href, texmlType, false) == null,
+ "should not detect text/xml non-feed");
+ ok(testIsFeed(rss, href, texmlType, false) == null,
+ "should not detect text/xml feed from type and title");
+ ok(testIsFeed(noRss, href, appxmlType, false) == null,
+ "should not detect application/xml non-feed");
+ ok(testIsFeed(rss, href, appxmlType, false) == null,
+ "should not detect application/xml feed from type and title");
+
+ // security check only, returns cleaned up type or "application/rss+xml"
+ ok(testIsFeed(noRss, href, atomType, true) == atomType,
+ "feed security check should return Atom type");
+ ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType,
+ "feed security check should return cleaned up Atom type");
+ ok(testIsFeed(noRss, href, rssType, true) == rssType,
+ "feed security check should return RSS type");
+ ok(testIsFeed(noRss, href, funkyRssType, true) == rssType,
+ "feed security check should return cleaned up RSS type");
+ ok(testIsFeed(noRss, href, "", true) == rssType,
+ "feed security check without type should return RSS type");
+ ok(testIsFeed(noRss, href, "garbage", true) == "garbage",
+ "feed security check with garbage type should return garbage");
+}
diff --git a/browser/base/content/test/general/browser_bug416661.js b/browser/base/content/test/general/browser_bug416661.js
new file mode 100644
index 000000000..a37971a34
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug416661.js
@@ -0,0 +1,43 @@
+var tabElm, zoomLevel;
+function start_test_prefNotSet() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, 1, "initial zoom level should be 1");
+ FullZoom.enlarge();
+
+ // capture the zoom level to test later
+ zoomLevel = ZoomManager.zoom;
+ isnot(zoomLevel, 1, "zoom level should have changed");
+
+ yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/general/moz.png");
+ }).then(continue_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
+
+function continue_test_prefNotSet () {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image");
+ yield FullZoom.reset();
+
+ yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/general/zoom_test.html");
+ }).then(end_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
+
+function end_test_prefNotSet() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted");
+
+ // Reset the zoom so that other tests have a fresh zoom level
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange();
+ finish();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ tabElm = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tabElm);
+ yield FullZoomHelper.load(tabElm, "http://mochi.test:8888/browser/browser/base/content/test/general/zoom_test.html");
+ }).then(start_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug417483.js b/browser/base/content/test/general/browser_bug417483.js
new file mode 100644
index 000000000..43ff7b917
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug417483.js
@@ -0,0 +1,30 @@
+add_task(function* () {
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true);
+ const htmlContent = "data:text/html, <iframe src='data:text/html,text text'></iframe>";
+ gBrowser.loadURI(htmlContent);
+ yield loadedPromise;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+ let frame = content.frames[0];
+ let sel = frame.getSelection();
+ let range = frame.document.createRange();
+ let tn = frame.document.body.childNodes[0];
+ range.setStart(tn, 4);
+ range.setEnd(tn, 5);
+ sel.addRange(range);
+ frame.focus();
+ });
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("frame", 5, 5,
+ { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+});
diff --git a/browser/base/content/test/general/browser_bug419612.js b/browser/base/content/test/general/browser_bug419612.js
new file mode 100644
index 000000000..8c34b2d39
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug419612.js
@@ -0,0 +1,32 @@
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ let testPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ let tab1 = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.load(tab1, testPage);
+
+ let tab2 = gBrowser.addTab();
+ yield FullZoomHelper.load(tab2, testPage);
+
+ FullZoom.enlarge();
+ let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
+ let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+ is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs");
+
+ gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false);
+ yield FullZoom.reset();
+ gBrowser.selectedTab = tab1;
+ tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+ tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+ isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs");
+
+ if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
+ gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug422590.js b/browser/base/content/test/general/browser_bug422590.js
new file mode 100644
index 000000000..f26919cc5
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug422590.js
@@ -0,0 +1,50 @@
+function test() {
+ waitForExplicitFinish();
+ // test the main (normal) browser window
+ testCustomize(window, testChromeless);
+}
+
+function testChromeless() {
+ // test a chromeless window
+ var newWin = openDialog(getBrowserURL(), "_blank",
+ "chrome,dialog=no,location=yes,toolbar=no", "about:blank");
+ ok(newWin, "got new window");
+
+ whenDelayedStartupFinished(newWin, function () {
+ // Check that the search bar is hidden
+ var searchBar = newWin.BrowserSearch.searchBar;
+ ok(searchBar, "got search bar");
+
+ var searchBarBO = searchBar.boxObject;
+ is(searchBarBO.width, 0, "search bar hidden");
+ is(searchBarBO.height, 0, "search bar hidden");
+
+ testCustomize(newWin, function () {
+ newWin.close();
+ finish();
+ });
+ });
+}
+
+function testCustomize(aWindow, aCallback) {
+ var fileMenu = aWindow.document.getElementById("file-menu");
+ ok(fileMenu, "got file menu");
+ is(fileMenu.disabled, false, "file menu initially enabled");
+
+ openToolbarCustomizationUI(function () {
+ // Can't use the property, since the binding may have since been removed
+ // if the element is hidden (see bug 422590)
+ is(fileMenu.getAttribute("disabled"), "true",
+ "file menu is disabled during toolbar customization");
+
+ closeToolbarCustomizationUI(onClose, aWindow);
+ }, aWindow);
+
+ function onClose() {
+ is(fileMenu.getAttribute("disabled"), "false",
+ "file menu is enabled after toolbar customization");
+
+ if (aCallback)
+ aCallback();
+ }
+}
diff --git a/browser/base/content/test/general/browser_bug423833.js b/browser/base/content/test/general/browser_bug423833.js
new file mode 100644
index 000000000..d4069338b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug423833.js
@@ -0,0 +1,138 @@
+/* Tests for proper behaviour of "Show this frame" context menu options */
+
+// Two frames, one with text content, the other an error page
+var invalidPage = 'http://127.0.0.1:55555/';
+var validPage = 'http://example.com/';
+var testPage = 'data:text/html,<frameset cols="400,400"><frame src="' + validPage + '"><frame src="' + invalidPage + '"></frameset>';
+
+// Store the tab and window created in tests 2 and 3 respectively
+var test2tab;
+var test3window;
+
+// We use setInterval instead of setTimeout to avoid race conditions on error doc loads
+var intervalID;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", test1Setup, true);
+ content.location = testPage;
+}
+
+function test1Setup() {
+ if (content.frames.length < 2 ||
+ content.frames[1].location != invalidPage)
+ // The error frame hasn't loaded yet
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("load", test1Setup, true);
+
+ var badFrame = content.frames[1];
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ // We'd like to use another load listener here, but error pages don't fire load events
+ contextMenu.showOnlyThisFrame();
+ intervalID = setInterval(testShowOnlyThisFrame, 3000);
+}
+
+function testShowOnlyThisFrame() {
+ if (content.location.href == testPage)
+ // This is a stale event from the original page loading
+ return;
+
+ // We should now have loaded the error page frame content directly
+ // in the tab, make sure the URL is right.
+ clearInterval(intervalID);
+
+ is(content.location.href, invalidPage, "Should navigate to page url, not about:neterror");
+
+ // Go back to the frames page
+ gBrowser.addEventListener("load", test2Setup, true);
+ content.location = testPage;
+}
+
+function test2Setup() {
+ if (content.frames.length < 2 ||
+ content.frames[1].location != invalidPage)
+ // The error frame hasn't loaded yet
+ return;
+
+ gBrowser.removeEventListener("load", test2Setup, true);
+
+ // Now let's do the whole thing again, but this time for "Open frame in new tab"
+ var badFrame = content.frames[1];
+
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ gBrowser.tabContainer.addEventListener("TabOpen", function (event) {
+ test2tab = event.target;
+ gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, false);
+ }, false);
+ contextMenu.openFrameInTab();
+ ok(test2tab, "openFrameInTab() opened a tab");
+
+ gBrowser.selectedTab = test2tab;
+
+ intervalID = setInterval(testOpenFrameInTab, 3000);
+}
+
+function testOpenFrameInTab() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ // Wait another cycle
+ return;
+
+ clearInterval(intervalID);
+
+ // We should now have the error page in a new, active tab.
+ is(gBrowser.contentDocument.location.href, invalidPage, "New tab should have page url, not about:neterror");
+
+ // Clear up the new tab, and punt to test 3
+ gBrowser.removeCurrentTab();
+
+ test3Setup();
+}
+
+function test3Setup() {
+ // One more time, for "Open frame in new window"
+ var badFrame = content.frames[1];
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened")
+ test3window = aSubject;
+ Services.ww.unregisterNotification(arguments.callee);
+ });
+
+ contextMenu.openFrame();
+
+ intervalID = setInterval(testOpenFrame, 3000);
+}
+
+function testOpenFrame() {
+ if (!test3window || test3window.content.location.href == "about:blank") {
+ info("testOpenFrame: Wait another cycle");
+ return;
+ }
+
+ clearInterval(intervalID);
+
+ is(test3window.content.location.href, invalidPage, "New window should have page url, not about:neterror");
+
+ test3window.close();
+ cleanup();
+}
+
+function cleanup() {
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug424101.js b/browser/base/content/test/general/browser_bug424101.js
new file mode 100644
index 000000000..8000d2ae9
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug424101.js
@@ -0,0 +1,52 @@
+/* Make sure that the context menu appears on form elements */
+
+add_task(function *() {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,test");
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ let tests = [
+ { element: "input", type: "text" },
+ { element: "input", type: "password" },
+ { element: "input", type: "image" },
+ { element: "input", type: "button" },
+ { element: "input", type: "submit" },
+ { element: "input", type: "reset" },
+ { element: "input", type: "checkbox" },
+ { element: "input", type: "radio" },
+ { element: "button" },
+ { element: "select" },
+ { element: "option" },
+ { element: "optgroup" }
+ ];
+
+ for (let index = 0; index < tests.length; index++) {
+ let test = tests[index];
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser,
+ { element: test.element, type: test.type, index: index },
+ function* (arg) {
+ let element = content.document.createElement(arg.element);
+ element.id = "element" + arg.index;
+ if (arg.type) {
+ element.setAttribute("type", arg.type);
+ }
+ content.document.body.appendChild(element);
+ });
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#element" + index,
+ { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ let typeAttr = test.type ? "type=" + test.type + " " : "";
+ is(gContextMenu.shouldDisplay, true,
+ "context menu behavior for <" + test.element + " " + typeAttr + "> is wrong");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug427559.js b/browser/base/content/test/general/browser_bug427559.js
new file mode 100644
index 000000000..78cecdefa
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug427559.js
@@ -0,0 +1,38 @@
+"use strict";
+
+/*
+ * Test bug 427559 to make sure focused elements that are no longer on the page
+ * will have focus transferred to the window when changing tabs back to that
+ * tab with the now-gone element.
+ */
+
+// Default focus on a button and have it kill itself on blur.
+const URL = 'data:text/html;charset=utf-8,' +
+ '<body><button onblur="this.remove()">' +
+ '<script>document.body.firstChild.focus()</script></body>';
+
+function getFocusedLocalName(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ return content.document.activeElement.localName;
+ });
+}
+
+add_task(function* () {
+ let testTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+ let browser = testTab.linkedBrowser;
+
+ is((yield getFocusedLocalName(browser)), "button", "button is focused");
+
+ let blankTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ yield BrowserTestUtils.switchTab(gBrowser, testTab);
+
+ // Make sure focus is given to the window because the element is now gone.
+ is((yield getFocusedLocalName(browser)), "body", "body is focused");
+
+ // Cleanup.
+ gBrowser.removeTab(blankTab);
+ gBrowser.removeCurrentTab();
+
+});
diff --git a/browser/base/content/test/general/browser_bug431826.js b/browser/base/content/test/general/browser_bug431826.js
new file mode 100644
index 000000000..592ea9cef
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug431826.js
@@ -0,0 +1,50 @@
+function remote(task) {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, task);
+}
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ let promise = remote(function () {
+ return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true, event => {
+ return content.document.documentURI != "about:blank";
+ }).then(() => 0); // don't want to send the event to the chrome process
+ });
+ gBrowser.loadURI("https://nocert.example.com/");
+ yield promise;
+
+ yield remote(() => {
+ // Confirm that we are displaying the contributed error page, not the default
+ let uri = content.document.documentURI;
+ Assert.ok(uri.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror");
+ });
+
+ yield remote(() => {
+ let div = content.document.getElementById("badCertAdvancedPanel");
+ // Confirm that the expert section is collapsed
+ Assert.ok(div, "Advanced content div should exist");
+ Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
+ "none", "Advanced content should not be visible by default");
+ });
+
+ // Tweak the expert mode pref
+ gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
+
+ promise = remote(function () {
+ return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true);
+ });
+ gBrowser.reload();
+ yield promise;
+
+ yield remote(() => {
+ let div = content.document.getElementById("badCertAdvancedPanel");
+ Assert.ok(div, "Advanced content div should exist");
+ Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
+ "block", "Advanced content should be visible by default");
+ });
+
+ // Clean up
+ gBrowser.removeCurrentTab();
+ if (gPrefService.prefHasUserValue("browser.xul.error_pages.expert_bad_cert"))
+ gPrefService.clearUserPref("browser.xul.error_pages.expert_bad_cert");
+});
diff --git a/browser/base/content/test/general/browser_bug432599.js b/browser/base/content/test/general/browser_bug432599.js
new file mode 100644
index 000000000..a5f7c0b5e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug432599.js
@@ -0,0 +1,127 @@
+function invokeUsingCtrlD(phase) {
+ switch (phase) {
+ case 1:
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ break;
+ case 2:
+ case 4:
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ break;
+ case 3:
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ break;
+ }
+}
+
+function invokeUsingStarButton(phase) {
+ switch (phase) {
+ case 1:
+ EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star, {});
+ break;
+ case 2:
+ case 4:
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ break;
+ case 3:
+ EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star,
+ { clickCount: 2 });
+ break;
+ }
+}
+
+var testURL = "data:text/plain,Content";
+var bookmarkId;
+
+function add_bookmark(aURI, aTitle) {
+ return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ aTitle);
+}
+
+// test bug 432599
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+ waitForStarChange(false, initTest);
+ }, true);
+
+ content.location = testURL;
+}
+
+function initTest() {
+ // First, bookmark the page.
+ bookmarkId = add_bookmark(makeURI(testURL), "Bug 432599 Test");
+
+ checkBookmarksPanel(invokers[currentInvoker], 1);
+}
+
+function waitForStarChange(aValue, aCallback) {
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(waitForStarChange, 50, aValue, aCallback);
+ return;
+ }
+ aCallback();
+}
+
+var invokers = [invokeUsingStarButton, invokeUsingCtrlD];
+var currentInvoker = 0;
+
+var initialValue;
+var initialRemoveHidden;
+
+var popupElement = document.getElementById("editBookmarkPanel");
+var titleElement = document.getElementById("editBookmarkPanelTitle");
+var removeElement = document.getElementById("editBookmarkPanelRemoveButton");
+
+function checkBookmarksPanel(invoker, phase)
+{
+ let onPopupShown = function(aEvent) {
+ if (aEvent.originalTarget == popupElement) {
+ popupElement.removeEventListener("popupshown", arguments.callee, false);
+ checkBookmarksPanel(invoker, phase + 1);
+ }
+ };
+ let onPopupHidden = function(aEvent) {
+ if (aEvent.originalTarget == popupElement) {
+ popupElement.removeEventListener("popuphidden", arguments.callee, false);
+ if (phase < 4) {
+ checkBookmarksPanel(invoker, phase + 1);
+ } else {
+ ++currentInvoker;
+ if (currentInvoker < invokers.length) {
+ checkBookmarksPanel(invokers[currentInvoker], 1);
+ } else {
+ gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
+ PlacesUtils.bookmarks.removeItem(bookmarkId);
+ executeSoon(finish);
+ }
+ }
+ }
+ };
+
+ switch (phase) {
+ case 1:
+ case 3:
+ popupElement.addEventListener("popupshown", onPopupShown, false);
+ break;
+ case 2:
+ popupElement.addEventListener("popuphidden", onPopupHidden, false);
+ initialValue = titleElement.value;
+ initialRemoveHidden = removeElement.hidden;
+ break;
+ case 4:
+ popupElement.addEventListener("popuphidden", onPopupHidden, false);
+ is(titleElement.value, initialValue, "The bookmark panel's title should be the same");
+ is(removeElement.hidden, initialRemoveHidden, "The bookmark panel's visibility should not change");
+ break;
+ }
+ invoker(phase);
+}
diff --git a/browser/base/content/test/general/browser_bug435035.js b/browser/base/content/test/general/browser_bug435035.js
new file mode 100644
index 000000000..7570ef0d7
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug435035.js
@@ -0,0 +1,17 @@
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ is(document.getElementById("identity-box").className,
+ "unknownIdentity mixedDisplayContent",
+ "identity box has class name for mixed content");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+
+ gBrowser.loadURI(
+ "https://example.com/browser/browser/base/content/test/general/test_bug435035.html"
+ );
+}
diff --git a/browser/base/content/test/general/browser_bug435325.js b/browser/base/content/test/general/browser_bug435325.js
new file mode 100644
index 000000000..2ae15deff
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug435325.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */
+
+var proxyPrefValue;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Go offline and disable the proxy and cache, then try to load the test URL.
+ Services.io.offline = true;
+
+ // Tests always connect to localhost, and per bug 87717, localhost is now
+ // reachable in offline mode. To avoid this, disable any proxy.
+ proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+ Services.prefs.setIntPref("network.proxy.type", 0);
+
+ Services.prefs.setBoolPref("browser.cache.disk.enable", false);
+ Services.prefs.setBoolPref("browser.cache.memory.enable", false);
+
+ gBrowser.selectedTab = gBrowser.addTab("http://example.com/");
+
+ let contentScript = `
+ let listener = function () {
+ removeEventListener("DOMContentLoaded", listener);
+ sendAsyncMessage("Test:DOMContentLoaded", { uri: content.document.documentURI });
+ };
+ addEventListener("DOMContentLoaded", listener);
+ `;
+
+ function pageloaded({ data }) {
+ mm.removeMessageListener("Test:DOMContentLoaded", pageloaded);
+ checkPage(data);
+ }
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("Test:DOMContentLoaded", pageloaded);
+ mm.loadFrameScript("data:," + contentScript, true);
+}
+
+function checkPage(data) {
+ ok(Services.io.offline, "Setting Services.io.offline to true.");
+
+ is(data.uri.substring(0, 27),
+ "about:neterror?e=netOffline", "Loading the Offline mode neterror page.");
+
+ // Re-enable the proxy so example.com is resolved to localhost, rather than
+ // the actual example.com.
+ Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
+
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
+ "online.");
+ Services.obs.removeObserver(observer, "network:offline-status-changed", false);
+ finish();
+ }, "network:offline-status-changed", false);
+
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ content.document.getElementById("errorTryAgain").click();
+ });
+}
+
+registerCleanupFunction(function() {
+ Services.prefs.setBoolPref("browser.cache.disk.enable", true);
+ Services.prefs.setBoolPref("browser.cache.memory.enable", true);
+ Services.io.offline = false;
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug441778.js b/browser/base/content/test/general/browser_bug441778.js
new file mode 100644
index 000000000..fa938541f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug441778.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test the fix for bug 441778 to ensure site-specific page zoom doesn't get
+ * modified by sub-document loads of content from a different domain.
+ */
+
+function test() {
+ waitForExplicitFinish();
+
+ const TEST_PAGE_URL = 'data:text/html,<body><iframe src=""></iframe></body>';
+ const TEST_IFRAME_URL = "http://test2.example.org/";
+
+ Task.spawn(function* () {
+ // Prepare the test tab
+ let tab = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
+
+ let testBrowser = tab.linkedBrowser;
+
+ yield FullZoomHelper.load(tab, TEST_PAGE_URL);
+
+ // Change the zoom level and then save it so we can compare it to the level
+ // after loading the sub-document.
+ FullZoom.enlarge();
+ var zoomLevel = ZoomManager.zoom;
+
+ // Start the sub-document load.
+ let deferred = Promise.defer();
+ executeSoon(function () {
+ BrowserTestUtils.browserLoaded(testBrowser, true).then(url => {
+ is(url, TEST_IFRAME_URL, "got the load event for the iframe");
+ is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load");
+
+ FullZoomHelper.removeTabAndWaitForLocationChange().
+ then(() => deferred.resolve());
+ });
+ ContentTask.spawn(testBrowser, TEST_IFRAME_URL, url => {
+ content.document.querySelector("iframe").src = url;
+ });
+ });
+ yield deferred.promise;
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug455852.js b/browser/base/content/test/general/browser_bug455852.js
new file mode 100644
index 000000000..ce883b581
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug455852.js
@@ -0,0 +1,20 @@
+add_task(function*() {
+ is(gBrowser.tabs.length, 1, "one tab is open");
+
+ gBrowser.selectedBrowser.focus();
+ isnot(document.activeElement, gURLBar.inputField, "location bar is not focused");
+
+ var tab = gBrowser.selectedTab;
+ gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+
+ let tabClosedPromise = BrowserTestUtils.removeTab(tab, {dontRemove: true});
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ yield tabClosedPromise;
+
+ is(tab.parentNode, null, "ctrl+w removes the tab");
+ is(gBrowser.tabs.length, 1, "a new tab has been opened");
+ is(document.activeElement, gURLBar.inputField, "location bar is focused for the new tab");
+
+ if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
+ gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
+});
diff --git a/browser/base/content/test/general/browser_bug460146.js b/browser/base/content/test/general/browser_bug460146.js
new file mode 100644
index 000000000..1fdf0921c
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug460146.js
@@ -0,0 +1,51 @@
+/* Check proper image url retrieval from all kinds of elements/styles */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab");
+
+ pageInfo.addEventListener("load", function () {
+ pageInfo.removeEventListener("load", arguments.callee, true);
+ pageInfo.onFinished.push(function () {
+ executeSoon(function () {
+ var imageTree = pageInfo.document.getElementById("imagetree");
+ var imageRowsNum = imageTree.view.rowCount;
+
+ ok(imageTree, "Image tree is null (media tab is broken)");
+
+ ok(imageRowsNum == 7, "Number of images listed: " +
+ imageRowsNum + ", should be 7");
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "data:text/html," +
+ "<html>" +
+ " <head>" +
+ " <title>Test for media tab</title>" +
+ " <link rel='shortcut icon' href='file:///dummy_icon.ico'>" + // Icon
+ " </head>" +
+ " <body style='background-image:url(about:logo?a);'>" + // Background
+ " <img src='file:///dummy_image.gif'>" + // Image
+ " <ul>" +
+ " <li style='list-style:url(about:logo?b);'>List Item 1</li>" + // Bullet
+ " </ul> " +
+ " <div style='-moz-border-image: url(about:logo?c) 20 20 20 20;'>test</div>" + // Border
+ " <a href='' style='cursor: url(about:logo?d),default;'>test link</a>" + // Cursor
+ " <object type='image/svg+xml' width=20 height=20 data='file:///dummy_object.svg'></object>" + // Object
+ " </body>" +
+ "</html>";
+}
diff --git a/browser/base/content/test/general/browser_bug462289.js b/browser/base/content/test/general/browser_bug462289.js
new file mode 100644
index 000000000..1ce79f07e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug462289.js
@@ -0,0 +1,81 @@
+var tab1, tab2;
+
+function focus_in_navbar()
+{
+ var parent = document.activeElement.parentNode;
+ while (parent && parent.id != "nav-bar")
+ parent = parent.parentNode;
+
+ return parent != null;
+}
+
+function test()
+{
+ waitForExplicitFinish();
+
+ tab1 = gBrowser.addTab("about:blank", {skipAnimation: true});
+ tab2 = gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ executeSoon(step2);
+}
+
+function step2()
+{
+ is(gBrowser.selectedTab, tab1, "1st click on tab1 selects tab");
+ isnot(document.activeElement, tab1, "1st click on tab1 does not activate tab");
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ executeSoon(step3);
+}
+
+function step3()
+{
+ is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected");
+ isnot(document.activeElement, tab1, "2nd click on selected tab1 does not activate tab");
+
+ ok(true, "focusing URLBar then sending 1 Shift+Tab.");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected");
+ is(document.activeElement, tab1, "tab key to selected tab1 activates tab");
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ executeSoon(step4);
+}
+
+function step4()
+{
+ is(gBrowser.selectedTab, tab1, "3rd click on activated tab1 keeps tab selected");
+ is(document.activeElement, tab1, "3rd click on activated tab1 keeps tab activated");
+
+ gBrowser.addEventListener("TabSwitchDone", step5);
+ EventUtils.synthesizeMouseAtCenter(tab2, {});
+}
+
+function step5()
+{
+ gBrowser.removeEventListener("TabSwitchDone", step5);
+
+ // The tabbox selects a tab within a setTimeout in a bubbling mousedown event
+ // listener, and focuses the current tab if another tab previously had focus.
+ is(gBrowser.selectedTab, tab2, "click on tab2 while tab1 is activated selects tab");
+ is(document.activeElement, tab2, "click on tab2 while tab1 is activated activates tab");
+
+ info("focusing content then sending middle-button mousedown to tab2.");
+ gBrowser.selectedBrowser.focus();
+
+ EventUtils.synthesizeMouseAtCenter(tab2, {button: 1, type: "mousedown"});
+ executeSoon(step6);
+}
+
+function step6()
+{
+ is(gBrowser.selectedTab, tab2, "middle-button mousedown on selected tab2 keeps tab selected");
+ isnot(document.activeElement, tab2, "middle-button mousedown on selected tab2 does not activate tab");
+
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab1);
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug462673.js b/browser/base/content/test/general/browser_bug462673.js
new file mode 100644
index 000000000..f5b090917
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug462673.js
@@ -0,0 +1,36 @@
+add_task(function* () {
+ var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+ yield SimpleTest.promiseFocus(win);
+
+ let tab = win.gBrowser.tabContainer.firstChild;
+ yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
+
+ is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
+ is(win.gBrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
+ win.gBrowser.removeTab(tab);
+
+ // Closing a tab will also close its parent chrome window, but async
+ yield promiseWindowWillBeClosed(win);
+});
+
+add_task(function* () {
+ var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+ yield SimpleTest.promiseFocus(win);
+
+ let tab = win.gBrowser.tabContainer.firstChild;
+ yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
+
+ var newTab = win.gBrowser.addTab();
+ var newBrowser = newTab.linkedBrowser;
+ win.gBrowser.removeTab(tab);
+ ok(!win.closed, "Window stays open");
+ if (!win.closed) {
+ is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab");
+ is(win.gBrowser.browsers.length, 1, "Window has one browser");
+ is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected");
+ is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
+ is(win.gBrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
+ }
+
+ yield promiseWindowClosed(win);
+});
diff --git a/browser/base/content/test/general/browser_bug477014.js b/browser/base/content/test/general/browser_bug477014.js
new file mode 100644
index 000000000..8a0fac6d8
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug477014.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// That's a gecko!
+const iconURLSpec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+var testPage="data:text/plain,test bug 477014";
+
+add_task(function*() {
+ let tabToDetach = gBrowser.addTab(testPage);
+ yield waitForDocLoadComplete(tabToDetach.linkedBrowser);
+
+ gBrowser.setIcon(tabToDetach, iconURLSpec,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ tabToDetach.setAttribute("busy", "true");
+
+ // detach and set the listener on the new window
+ let newWindow = gBrowser.replaceTabWithWindow(tabToDetach);
+ yield promiseWaitForEvent(tabToDetach.linkedBrowser, "SwapDocShells");
+
+ is(newWindow.gBrowser.selectedTab.hasAttribute("busy"), true, "Busy attribute should be correct");
+ is(newWindow.gBrowser.getIcon(), iconURLSpec, "Icon should be correct");
+
+ newWindow.close();
+});
diff --git a/browser/base/content/test/general/browser_bug479408.js b/browser/base/content/test/general/browser_bug479408.js
new file mode 100644
index 000000000..0dfa96f2e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug479408.js
@@ -0,0 +1,17 @@
+function test() {
+ waitForExplicitFinish();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(
+ "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug479408_sample.html");
+
+ gBrowser.addEventListener("DOMLinkAdded", function(aEvent) {
+ gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, true);
+
+ executeSoon(function() {
+ ok(!tab.linkedBrowser.engines,
+ "the subframe's search engine wasn't detected");
+
+ gBrowser.removeTab(tab);
+ finish();
+ });
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug479408_sample.html b/browser/base/content/test/general/browser_bug479408_sample.html
new file mode 100644
index 000000000..f83f02bb9
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug479408_sample.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>Testcase for bug 479408</title>
+
+<iframe src='data:text/html,<link%20rel="search"%20type="application/opensearchdescription+xml"%20title="Search%20bug%20479408"%20href="http://example.com/search.xml">'>
diff --git a/browser/base/content/test/general/browser_bug481560.js b/browser/base/content/test/general/browser_bug481560.js
new file mode 100644
index 000000000..bb9249e75
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug481560.js
@@ -0,0 +1,21 @@
+function test() {
+ waitForExplicitFinish();
+
+ whenNewWindowLoaded(null, function (win) {
+ waitForFocus(function () {
+ function onTabClose() {
+ ok(false, "shouldn't have gotten the TabClose event for the last tab");
+ }
+ var tab = win.gBrowser.selectedTab;
+ tab.addEventListener("TabClose", onTabClose, false);
+
+ EventUtils.synthesizeKey("w", { accelKey: true }, win);
+
+ ok(win.closed, "accel+w closed the window immediately");
+
+ tab.removeEventListener("TabClose", onTabClose, false);
+
+ finish();
+ }, win);
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug484315.js b/browser/base/content/test/general/browser_bug484315.js
new file mode 100644
index 000000000..fb23ae33a
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug484315.js
@@ -0,0 +1,23 @@
+function test() {
+ var contentWin = window.open("about:blank", "", "width=100,height=100");
+ var enumerator = Services.wm.getEnumerator("navigator:browser");
+
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ if (win.content == contentWin) {
+ gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+ win.gBrowser.removeCurrentTab();
+ ok(win.closed, "popup is closed");
+
+ // clean up
+ if (!win.closed)
+ win.close();
+ if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
+ gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
+
+ return;
+ }
+ }
+
+ throw "couldn't find the content window";
+}
diff --git a/browser/base/content/test/general/browser_bug491431.js b/browser/base/content/test/general/browser_bug491431.js
new file mode 100644
index 000000000..d270e912e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug491431.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var testPage = "data:text/plain,test bug 491431 Page";
+
+function test() {
+ waitForExplicitFinish();
+
+ let newWin, tabA, tabB;
+
+ // test normal close
+ tabA = gBrowser.addTab(testPage);
+ gBrowser.tabContainer.addEventListener("TabClose", function(firstTabCloseEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
+ ok(!firstTabCloseEvent.detail.adoptedBy, "This was a normal tab close");
+
+ // test tab close by moving
+ tabB = gBrowser.addTab(testPage);
+ gBrowser.tabContainer.addEventListener("TabClose", function(secondTabCloseEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
+ executeSoon(function() {
+ ok(secondTabCloseEvent.detail.adoptedBy, "This was a tab closed by moving");
+
+ // cleanup
+ newWin.close();
+ executeSoon(finish);
+ });
+ }, true);
+ newWin = gBrowser.replaceTabWithWindow(tabB);
+ }, true);
+ gBrowser.removeTab(tabA);
+}
+
diff --git a/browser/base/content/test/general/browser_bug495058.js b/browser/base/content/test/general/browser_bug495058.js
new file mode 100644
index 000000000..a82c6c931
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug495058.js
@@ -0,0 +1,38 @@
+/**
+ * Tests that the right elements of a tab are focused when it is
+ * torn out into its own window.
+ */
+
+const URIS = [
+ "about:blank",
+ "about:sessionrestore",
+ "about:privatebrowsing",
+];
+
+add_task(function*() {
+ for (let uri of URIS) {
+ let tab = gBrowser.addTab();
+ yield BrowserTestUtils.loadURI(tab.linkedBrowser, uri);
+
+ let win = gBrowser.replaceTabWithWindow(tab);
+ yield TestUtils.topicObserved("browser-delayed-startup-finished",
+ subject => subject == win);
+ tab = win.gBrowser.selectedTab;
+
+ // BrowserTestUtils doesn't get the add-on shims, which means that
+ // MozAfterPaint won't get shimmed over if we add an event handler
+ // for it in the parent.
+ if (tab.linkedBrowser.isRemoteBrowser) {
+ yield BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "MozAfterPaint");
+ } else {
+ yield BrowserTestUtils.waitForEvent(tab.linkedBrowser, "MozAfterPaint");
+ }
+
+ Assert.equal(win.gBrowser.currentURI.spec, uri, uri + ": uri loaded in detached tab");
+ Assert.equal(win.document.activeElement, win.gBrowser.selectedBrowser, uri + ": browser is focused");
+ Assert.equal(win.gURLBar.value, "", uri + ": urlbar is empty");
+ Assert.ok(win.gURLBar.placeholder, uri + ": placeholder text is present");
+
+ yield BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/browser/base/content/test/general/browser_bug517902.js b/browser/base/content/test/general/browser_bug517902.js
new file mode 100644
index 000000000..bc1d16f4b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug517902.js
@@ -0,0 +1,42 @@
+/* Make sure that "View Image Info" loads the correct image data */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ var doc = gBrowser.contentDocument;
+ var testImg = doc.getElementById("test-image");
+ var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab", testImg);
+
+ pageInfo.addEventListener("load", function () {
+ pageInfo.removeEventListener("load", arguments.callee, true);
+ pageInfo.onFinished.push(function () {
+ executeSoon(function () {
+ var pageInfoImg = pageInfo.document.getElementById("thepreviewimage");
+
+ is(pageInfoImg.src, testImg.src, "selected image has the correct source");
+ is(pageInfoImg.width, testImg.width, "selected image has the correct width");
+ is(pageInfoImg.height, testImg.height, "selected image has the correct height");
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "data:text/html," +
+ "<style type='text/css'>%23test-image,%23not-test-image {background-image: url('about:logo?c');}</style>" +
+ "<img src='about:logo?b' height=300 width=350 alt=2 id='not-test-image'>" +
+ "<img src='about:logo?b' height=300 width=350 alt=2>" +
+ "<img src='about:logo?a' height=200 width=250>" +
+ "<img src='about:logo?b' height=200 width=250 alt=1>" +
+ "<img src='about:logo?b' height=100 width=150 alt=2 id='test-image'>";
+}
diff --git a/browser/base/content/test/general/browser_bug519216.js b/browser/base/content/test/general/browser_bug519216.js
new file mode 100644
index 000000000..d3a517086
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug519216.js
@@ -0,0 +1,45 @@
+function test() {
+ waitForExplicitFinish();
+ gBrowser.addProgressListener(progressListener1);
+ gBrowser.addProgressListener(progressListener2);
+ gBrowser.addProgressListener(progressListener3);
+ gBrowser.loadURI("data:text/plain,bug519216");
+}
+
+var calledListener1 = false;
+var progressListener1 = {
+ onLocationChange: function onLocationChange() {
+ calledListener1 = true;
+ gBrowser.removeProgressListener(this);
+ }
+};
+
+var calledListener2 = false;
+var progressListener2 = {
+ onLocationChange: function onLocationChange() {
+ ok(calledListener1, "called progressListener1 before progressListener2");
+ calledListener2 = true;
+ gBrowser.removeProgressListener(this);
+ }
+};
+
+var progressListener3 = {
+ onLocationChange: function onLocationChange() {
+ ok(calledListener2, "called progressListener2 before progressListener3");
+ gBrowser.removeProgressListener(this);
+ gBrowser.addProgressListener(progressListener4);
+ executeSoon(function () {
+ expectListener4 = true;
+ gBrowser.reload();
+ });
+ }
+};
+
+var expectListener4 = false;
+var progressListener4 = {
+ onLocationChange: function onLocationChange() {
+ ok(expectListener4, "didn't call progressListener4 for the first location change");
+ gBrowser.removeProgressListener(this);
+ executeSoon(finish);
+ }
+};
diff --git a/browser/base/content/test/general/browser_bug520538.js b/browser/base/content/test/general/browser_bug520538.js
new file mode 100644
index 000000000..e0b64db9d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug520538.js
@@ -0,0 +1,15 @@
+function test() {
+ var tabCount = gBrowser.tabs.length;
+ gBrowser.selectedBrowser.focus();
+ browserDOMWindow.openURI(makeURI("about:blank"),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+ is(gBrowser.tabs.length, tabCount + 1,
+ "'--new-tab about:blank' opens a new tab");
+ is(gBrowser.selectedTab, gBrowser.tabs[tabCount],
+ "'--new-tab about:blank' selects the new tab");
+ is(document.activeElement, gURLBar.inputField,
+ "'--new-tab about:blank' focuses the location bar");
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/general/browser_bug521216.js b/browser/base/content/test/general/browser_bug521216.js
new file mode 100644
index 000000000..735ae92f6
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug521216.js
@@ -0,0 +1,50 @@
+var expected = ["TabOpen", "onStateChange", "onLocationChange", "onLinkIconAvailable"];
+var actual = [];
+var tabIndex = -1;
+this.__defineGetter__("tab", () => gBrowser.tabs[tabIndex]);
+
+function test() {
+ waitForExplicitFinish();
+ tabIndex = gBrowser.tabs.length;
+ gBrowser.addTabsProgressListener(progressListener);
+ gBrowser.tabContainer.addEventListener("TabOpen", TabOpen, false);
+ gBrowser.addTab("data:text/html,<html><head><link href='about:logo' rel='shortcut icon'>");
+}
+
+function record(aName) {
+ info("got " + aName);
+ if (actual.indexOf(aName) == -1)
+ actual.push(aName);
+ if (actual.length == expected.length) {
+ is(actual.toString(), expected.toString(),
+ "got events and progress notifications in expected order");
+
+ executeSoon(function(tab) {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTabsProgressListener(progressListener);
+ gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
+ finish();
+ }.bind(null, tab));
+ }
+}
+
+function TabOpen(aEvent) {
+ if (aEvent.target == tab)
+ record(arguments.callee.name);
+}
+
+var progressListener = {
+ onLocationChange: function onLocationChange(aBrowser) {
+ if (aBrowser == tab.linkedBrowser)
+ record(arguments.callee.name);
+ },
+ onStateChange: function onStateChange(aBrowser) {
+ if (aBrowser == tab.linkedBrowser)
+ record(arguments.callee.name);
+ },
+ onLinkIconAvailable: function onLinkIconAvailable(aBrowser, aIconURL) {
+ if (aBrowser == tab.linkedBrowser &&
+ aIconURL == "about:logo")
+ record(arguments.callee.name);
+ }
+};
diff --git a/browser/base/content/test/general/browser_bug533232.js b/browser/base/content/test/general/browser_bug533232.js
new file mode 100644
index 000000000..6c7a0e51f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug533232.js
@@ -0,0 +1,36 @@
+function test() {
+ var tab1 = gBrowser.selectedTab;
+ var tab2 = gBrowser.addTab();
+ var childTab1;
+ var childTab2;
+
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(tab1),
+ "closing a tab next to its parent selects the parent");
+
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = tab2;
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(tab2),
+ "closing a tab next to its parent doesn't select the parent if another tab had been selected ad interim");
+
+ gBrowser.selectedTab = tab1;
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ childTab2 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(childTab2),
+ "closing a tab next to its parent selects the next tab with the same parent");
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(tab2),
+ "closing the last tab in a set of child tabs doesn't go back to the parent");
+
+ gBrowser.removeTab(tab2, { skipPermitUnload: true });
+}
+
+function idx(tab) {
+ return Array.indexOf(gBrowser.tabs, tab);
+}
diff --git a/browser/base/content/test/general/browser_bug537013.js b/browser/base/content/test/general/browser_bug537013.js
new file mode 100644
index 000000000..5ae1586ea
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug537013.js
@@ -0,0 +1,135 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests for bug 537013 to ensure proper tab-sequestration of find bar. */
+
+var tabs = [];
+var texts = [
+ "This side up.",
+ "The world is coming to an end. Please log off.",
+ "Klein bottle for sale. Inquire within.",
+ "To err is human; to forgive is not company policy."
+];
+
+var Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+var HasFindClipboard = Clipboard.supportsFindClipboard();
+
+function addTabWithText(aText, aCallback) {
+ let newTab = gBrowser.addTab("data:text/html;charset=utf-8,<h1 id='h1'>" +
+ aText + "</h1>");
+ tabs.push(newTab);
+ gBrowser.selectedTab = newTab;
+}
+
+function setFindString(aString) {
+ gFindBar.open();
+ gFindBar._findField.focus();
+ gFindBar._findField.select();
+ EventUtils.sendString(aString);
+ is(gFindBar._findField.value, aString, "Set the field correctly!");
+}
+
+var newWindow;
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function () {
+ while (tabs.length) {
+ gBrowser.removeTab(tabs.pop());
+ }
+ });
+ texts.forEach(aText => addTabWithText(aText));
+
+ // Set up the first tab
+ gBrowser.selectedTab = tabs[0];
+
+ setFindString(texts[0]);
+ // Turn on highlight for testing bug 891638
+ gFindBar.toggleHighlight(true);
+
+ // Make sure the second tab is correct, then set it up
+ gBrowser.selectedTab = tabs[1];
+ gBrowser.selectedTab.addEventListener("TabFindInitialized", continueTests1);
+ // Initialize the findbar
+ gFindBar;
+}
+function continueTests1() {
+ gBrowser.selectedTab.removeEventListener("TabFindInitialized",
+ continueTests1);
+ ok(true, "'TabFindInitialized' event properly dispatched!");
+ ok(gFindBar.hidden, "Second tab doesn't show find bar!");
+ gFindBar.open();
+ is(gFindBar._findField.value, texts[0],
+ "Second tab kept old find value for new initialization!");
+ setFindString(texts[1]);
+
+ // Confirm the first tab is still correct, ensure re-hiding works as expected
+ gBrowser.selectedTab = tabs[0];
+ ok(!gFindBar.hidden, "First tab shows find bar!");
+ // When the Find Clipboard is supported, this test not relevant.
+ if (!HasFindClipboard)
+ is(gFindBar._findField.value, texts[0], "First tab persists find value!");
+ ok(gFindBar.getElement("highlight").checked,
+ "Highlight button state persists!");
+
+ // While we're here, let's test the backout of bug 253793.
+ gBrowser.reload();
+ gBrowser.addEventListener("DOMContentLoaded", continueTests2, true);
+}
+
+function continueTests2() {
+ gBrowser.removeEventListener("DOMContentLoaded", continueTests2, true);
+ ok(gFindBar.getElement("highlight").checked, "Highlight never reset!");
+ continueTests3();
+}
+
+function continueTests3() {
+ ok(gFindBar.getElement("highlight").checked, "Highlight button reset!");
+ gFindBar.close();
+ ok(gFindBar.hidden, "First tab doesn't show find bar!");
+ gBrowser.selectedTab = tabs[1];
+ ok(!gFindBar.hidden, "Second tab shows find bar!");
+ // Test for bug 892384
+ is(gFindBar._findField.getAttribute("focused"), "true",
+ "Open findbar refocused on tab change!");
+ gURLBar.focus();
+ gBrowser.selectedTab = tabs[0];
+ ok(gFindBar.hidden, "First tab doesn't show find bar!");
+
+ // Set up a third tab, no tests here
+ gBrowser.selectedTab = tabs[2];
+ setFindString(texts[2]);
+
+ // Now we jump to the second, then first, and then fourth
+ gBrowser.selectedTab = tabs[1];
+ // Test for bug 892384
+ ok(!gFindBar._findField.hasAttribute("focused"),
+ "Open findbar not refocused on tab change!");
+ gBrowser.selectedTab = tabs[0];
+ gBrowser.selectedTab = tabs[3];
+ ok(gFindBar.hidden, "Fourth tab doesn't show find bar!");
+ is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!");
+ gFindBar.open();
+ // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug.
+ if (!HasFindClipboard) {
+ is(gFindBar._findField.value, texts[1],
+ "Fourth tab has second tab's find value!");
+ }
+
+ newWindow = gBrowser.replaceTabWithWindow(tabs.pop());
+ whenDelayedStartupFinished(newWindow, checkNewWindow);
+}
+
+// Test that findbar gets restored when a tab is moved to a new window.
+function checkNewWindow() {
+ ok(!newWindow.gFindBar.hidden, "New window shows find bar!");
+ // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug.
+ if (!HasFindClipboard) {
+ is(newWindow.gFindBar._findField.value, texts[1],
+ "New window find bar has correct find value!");
+ }
+ ok(!newWindow.gFindBar.getElement("find-next").disabled,
+ "New window findbar has enabled buttons!");
+ newWindow.close();
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug537474.js b/browser/base/content/test/general/browser_bug537474.js
new file mode 100644
index 000000000..f1139f235
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug537474.js
@@ -0,0 +1,8 @@
+add_task(function *() {
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ browserDOMWindow.openURI(makeURI("about:"), null,
+ Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null)
+ yield browserLoadedPromise;
+ is(gBrowser.currentURI.spec, "about:", "page loads in the current content window");
+});
+
diff --git a/browser/base/content/test/general/browser_bug550565.js b/browser/base/content/test/general/browser_bug550565.js
new file mode 100644
index 000000000..b0e094e07
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug550565.js
@@ -0,0 +1,44 @@
+add_task(function* test() {
+ let testPath = getRootDirectory(gTestPath);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (tabBrowser) {
+ const URI = testPath + "file_with_favicon.html";
+ const expectedIcon = testPath + "file_generic_favicon.ico";
+
+ let got_favicon = Promise.defer();
+ let listener = {
+ onLinkIconAvailable(browser, iconURI) {
+ if (got_favicon && iconURI && browser === tabBrowser) {
+ got_favicon.resolve(iconURI);
+ got_favicon = null;
+ }
+ }
+ };
+ gBrowser.addTabsProgressListener(listener);
+
+ BrowserTestUtils.loadURI(tabBrowser, URI);
+
+ let iconURI = yield got_favicon.promise;
+ is(iconURI, expectedIcon, "Correct icon before pushState.");
+
+ got_favicon = Promise.defer();
+ got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e);
+ yield ContentTask.spawn(tabBrowser, null, function() {
+ content.history.pushState("page2", "page2", "page2");
+ });
+
+ // We've navigated and shouldn't get a call to onLinkIconAvailable.
+ TestUtils.executeSoon(() => {
+ got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser)));
+ });
+ try {
+ yield got_favicon.promise;
+ } catch (e) {
+ iconURI = e;
+ }
+ is(iconURI, expectedIcon, "Correct icon after pushState.");
+
+ gBrowser.removeTabsProgressListener(listener);
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug553455.js b/browser/base/content/test/general/browser_bug553455.js
new file mode 100644
index 000000000..c29a810de
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -0,0 +1,1200 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
+const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
+const PROGRESS_NOTIFICATION = "addon-progress";
+
+const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm");
+
+var rootDir = getRootDirectory(gTestPath);
+var rootPath = rootDir.split('/');
+var chromeName = rootPath[0] + '//' + rootPath[2];
+var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
+var jar = getJar(croot);
+if (jar) {
+ var tmpdir = extractJarToTmp(jar);
+ croot = 'file://' + tmpdir.path + '/';
+}
+const CHROMEROOT = croot;
+
+var gApp = document.getElementById("bundle_brand").getString("brandShortName");
+var gVersion = Services.appinfo.version;
+
+function getObserverTopic(aNotificationId) {
+ let topic = aNotificationId;
+ if (topic == "xpinstall-disabled")
+ topic = "addon-install-disabled";
+ else if (topic == "addon-progress")
+ topic = "addon-install-started";
+ else if (topic == "addon-install-restart")
+ topic = "addon-install-complete";
+ return topic;
+}
+
+function waitForProgressNotification(aPanelOpen = false, aExpectedCount = 1) {
+ return Task.spawn(function* () {
+ let notificationId = PROGRESS_NOTIFICATION;
+ info("Waiting for " + notificationId + " notification");
+
+ let topic = getObserverTopic(notificationId);
+
+ let observerPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ // Ignore the progress notification unless that is the notification we want
+ if (notificationId != PROGRESS_NOTIFICATION &&
+ aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) {
+ return;
+ }
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }, topic, false);
+ });
+
+ let panelEventPromise;
+ if (aPanelOpen) {
+ panelEventPromise = Promise.resolve();
+ } else {
+ panelEventPromise = new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("popupshowing", function eventListener() {
+ PopupNotifications.panel.removeEventListener("popupshowing", eventListener);
+ resolve();
+ });
+ });
+ }
+
+ yield observerPromise;
+ yield panelEventPromise;
+
+ info("Saw a notification");
+ ok(PopupNotifications.isPanelOpen, "Panel should be open");
+ is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
+ if (PopupNotifications.panel.childNodes.length) {
+ let nodes = Array.from(PopupNotifications.panel.childNodes);
+ let notification = nodes.find(n => n.id == notificationId + "-notification");
+ ok(notification, `Should have seen the right notification`);
+ }
+
+ return PopupNotifications.panel;
+ });
+}
+
+function waitForNotification(aId, aExpectedCount = 1) {
+ return Task.spawn(function* () {
+ info("Waiting for " + aId + " notification");
+
+ let topic = getObserverTopic(aId);
+
+ let observerPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ // Ignore the progress notification unless that is the notification we want
+ if (aId != PROGRESS_NOTIFICATION &&
+ aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) {
+ return;
+ }
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }, topic, false);
+ });
+
+ let panelEventPromise = new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("PanelUpdated", function eventListener(e) {
+ // Skip notifications that are not the one that we are supposed to be looking for
+ if (e.detail.indexOf(aId) == -1) {
+ return;
+ }
+ PopupNotifications.panel.removeEventListener("PanelUpdated", eventListener);
+ resolve();
+ });
+ });
+
+ yield observerPromise;
+ yield panelEventPromise;
+
+ info("Saw a notification");
+ ok(PopupNotifications.isPanelOpen, "Panel should be open");
+ is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
+ if (PopupNotifications.panel.childNodes.length) {
+ let nodes = Array.from(PopupNotifications.panel.childNodes);
+ let notification = nodes.find(n => n.id == aId + "-notification");
+ ok(notification, `Should have seen the right notification`);
+ }
+
+ return PopupNotifications.panel;
+ });
+}
+
+function waitForNotificationClose() {
+ return new Promise(resolve => {
+ info("Waiting for notification to close");
+ PopupNotifications.panel.addEventListener("popuphidden", function listener() {
+ PopupNotifications.panel.removeEventListener("popuphidden", listener, false);
+ resolve();
+ }, false);
+ });
+}
+
+function waitForInstallDialog() {
+ return Task.spawn(function* () {
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ yield waitForNotification("addon-install-confirmation");
+ return;
+ }
+
+ info("Waiting for install dialog");
+
+ let window = yield new Promise(resolve => {
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+ resolve(aXULWindow);
+ },
+ onCloseWindow: function(aXULWindow) {
+ },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {
+ }
+ });
+ });
+ info("Install dialog opened, waiting for focus");
+
+ let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ yield new Promise(resolve => {
+ waitForFocus(function() {
+ resolve();
+ }, domwindow);
+ });
+ info("Saw install dialog");
+ is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
+
+ // Override the countdown timer on the accept button
+ let button = domwindow.document.documentElement.getButton("accept");
+ button.disabled = false;
+
+ return;
+ });
+}
+
+function removeTab() {
+ return Promise.all([
+ waitForNotificationClose(),
+ BrowserTestUtils.removeTab(gBrowser.selectedTab)
+ ]);
+}
+
+function acceptInstallDialog() {
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ document.getElementById("addon-install-confirmation-accept").click();
+ } else {
+ let win = Services.wm.getMostRecentWindow("Addons:Install");
+ win.document.documentElement.acceptDialog();
+ }
+}
+
+function cancelInstallDialog() {
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ document.getElementById("addon-install-confirmation-cancel").click();
+ } else {
+ let win = Services.wm.getMostRecentWindow("Addons:Install");
+ win.document.documentElement.cancelDialog();
+ }
+}
+
+function waitForSingleNotification(aCallback) {
+ return Task.spawn(function* () {
+ while (PopupNotifications.panel.childNodes.length == 2) {
+ yield new Promise(resolve => executeSoon(resolve));
+
+ info("Waiting for single notification");
+ // Notification should never close while we wait
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ }
+ });
+}
+
+function setupRedirect(aSettings) {
+ var url = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs?mode=setup";
+ for (var name in aSettings) {
+ url += "&" + name + "=" + aSettings[name];
+ }
+
+ var req = new XMLHttpRequest();
+ req.open("GET", url, false);
+ req.send(null);
+}
+
+function getInstalls() {
+ return new Promise(resolve => {
+ AddonManager.getAllInstalls(installs => resolve(installs));
+ });
+}
+
+var TESTS = [
+function test_disabledInstall() {
+ return Task.spawn(function* () {
+ Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+ let notificationPromise = waitForNotification("xpinstall-disabled");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Enable", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "Software installation is currently disabled. Click Enable and try again.");
+
+ let closePromise = waitForNotificationClose();
+ // Click on Enable
+ EventUtils.synthesizeMouseAtCenter(notification.button, {});
+ yield closePromise;
+
+ try {
+ ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled");
+ }
+ catch (e) {
+ ok(false, "xpinstall.enabled should be set");
+ }
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Shouldn't be any pending installs");
+ });
+},
+
+function test_blockedInstall() {
+ return Task.spawn(function* () {
+ let notificationPromise = waitForNotification("addon-install-blocked");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Allow", "Should have seen the right button");
+ is(notification.getAttribute("origin"), "example.com",
+ "Should have seen the right origin host");
+ is(notification.getAttribute("label"),
+ gApp + " prevented this site from asking you to install software on your computer.",
+ "Should have seen the right message");
+
+ let dialogPromise = waitForInstallDialog();
+ // Click on Allow
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+ // Notification should have changed to progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ yield dialogPromise;
+
+ notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ panel = yield notificationPromise;
+
+ notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+ yield removeTab();
+ });
+},
+
+function test_whitelistedInstall() {
+ return Task.spawn(function* () {
+ let originalTab = gBrowser.selectedTab;
+ let tab;
+ gBrowser.selectedTab = originalTab;
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?"
+ + triggers).then(newTab => tab = newTab);
+ yield progressPromise;
+ yield dialogPromise;
+ yield BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present");
+
+ is(gBrowser.selectedTab, tab,
+ "tab selected in response to the addon-install-confirmation notification");
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_failedDownload() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let failPromise = waitForNotification("addon-install-failed");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "missing.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ let panel = yield failPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "The add-on could not be downloaded because of a connection failure.",
+ "Should have seen the right message");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_corruptFile() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let failPromise = waitForNotification("addon-install-failed");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "corrupt.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ let panel = yield failPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "The add-on downloaded from this site could not be installed " +
+ "because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_incompatible() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let failPromise = waitForNotification("addon-install-failed");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "incompatible.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ let panel = yield failPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "XPI Test could not be installed because it is not compatible with " +
+ gApp + " " + gVersion + ".",
+ "Should have seen the right message");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_restartless() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "restartless.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-complete");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "XPI Test has been installed successfully.",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending installs");
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", result => {
+ resolve(result);
+ });
+ });
+ addon.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+
+ let closePromise = waitForNotificationClose();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ yield closePromise;
+ });
+},
+
+function test_multiple() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Unsigned XPI": "amosigned.xpi",
+ "Restartless XPI": "restartless.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "2 add-ons will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function (result) {
+ resolve(result);
+ });
+ });
+ addon.uninstall();
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_sequential() {
+ return Task.spawn(function* () {
+ // This test is only relevant if using the new doorhanger UI
+ if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Restartless XPI": "restartless.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ // Should see the right add-on
+ let container = document.getElementById("addon-install-confirmation-content");
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+ progressPromise = waitForProgressNotification(true, 2);
+ triggers = encodeURIComponent(JSON.stringify({
+ "Theme XPI": "theme.xpi"
+ }));
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+
+ // Should still have the right add-on in the confirmation notification
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+ // Wait for the install to complete, we won't see a new confirmation
+ // notification
+ yield new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "addon-install-confirmation");
+ resolve();
+ }, "addon-install-confirmation", false);
+ });
+
+ // Make sure browser-addons.js executes first
+ yield new Promise(resolve => executeSoon(resolve));
+
+ // Should have dropped the progress notification
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
+ is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
+ "Should only be showing one install confirmation");
+
+ // Should still have the right add-on in the confirmation notification
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+ cancelInstallDialog();
+
+ ok(PopupNotifications.isPanelOpen, "Panel should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
+ is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
+ "Should still have an install confirmation open");
+
+ // Should have the next add-on's confirmation dialog
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
+
+ Services.perms.remove(makeURI("http://example.com"), "install");
+ let closePromise = waitForNotificationClose();
+ cancelInstallDialog();
+ yield closePromise;
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+},
+
+function test_someUnverified() {
+ return Task.spawn(function* () {
+ // This test is only relevant if using the new doorhanger UI and allowing
+ // unsigned add-ons
+ if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+ Preferences.get("xpinstall.signatures.required", true) ||
+ REQUIRE_SIGNING) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Extension XPI": "restartless-unsigned.xpi",
+ "Theme XPI": "theme.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ let message = notification.getAttribute("label");
+ is(message, "Caution: This site would like to install 2 add-ons in " + gApp +
+ ", some of which are unverified. Proceed at your own risk.",
+ "Should see the right message");
+
+ let container = document.getElementById("addon-install-confirmation-content");
+ is(container.childNodes.length, 2, "Should be two items listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+ is(container.childNodes[0].lastChild.getAttribute("class"),
+ "addon-install-confirmation-unsigned", "Should have the unverified marker");
+ is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
+ is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let [addon, theme] = yield new Promise(resolve => {
+ AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
+ "theme-xpi@tests.mozilla.org"],
+ function(addons) {
+ resolve(addons);
+ });
+ });
+ addon.uninstall();
+ // Installing a new theme tries to switch to it, switch back to the
+ // default theme.
+ theme.userDisabled = true;
+ theme.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_allUnverified() {
+ return Task.spawn(function* () {
+ // This test is only relevant if using the new doorhanger UI and allowing
+ // unsigned add-ons
+ if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+ Preferences.get("xpinstall.signatures.required", true) ||
+ REQUIRE_SIGNING) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Extension XPI": "restartless-unsigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ let message = notification.getAttribute("label");
+ is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
+
+ let container = document.getElementById("addon-install-confirmation-content");
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+ is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
+
+ let notificationPromise = waitForNotification("addon-install-complete");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(result) {
+ resolve(result);
+ });
+ });
+ addon.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_url() {
+ return Task.spawn(function* () {
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ yield removeTab();
+ });
+},
+
+function test_localFile() {
+ return Task.spawn(function* () {
+ let cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIChromeRegistry);
+ let path;
+ try {
+ path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
+ } catch (ex) {
+ path = CHROMEROOT + "corrupt.xpi";
+ }
+
+ let failPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "addon-install-failed");
+ resolve();
+ }, "addon-install-failed", false);
+ });
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(path);
+ yield failPromise;
+
+ // Wait for the browser code to add the failure notification
+ yield waitForSingleNotification();
+
+ let notification = PopupNotifications.panel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "This add-on could not be installed because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ yield removeTab();
+ });
+},
+
+function test_tabClose() {
+ return Task.spawn(function* () {
+ if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ runNextTest();
+ return;
+ }
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+ yield progressPromise;
+ yield dialogPromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+
+ let closePromise = waitForNotificationClose();
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield closePromise;
+
+ installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending install since the tab is closed");
+ });
+},
+
+// Add-ons should be cancelled and the install notification destroyed when
+// navigating to a new origin
+function test_tabNavigate() {
+ return Task.spawn(function* () {
+ if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Extension XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let closePromise = waitForNotificationClose();
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI("about:blank");
+ yield closePromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending install");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+},
+
+function test_urlBar() {
+ return Task.spawn(function* () {
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gURLBar.value = TESTROOT + "amosigned.xpi";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ yield progressPromise;
+ let installDialog = yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog(installDialog);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ yield removeTab();
+ });
+},
+
+function test_wrongHost() {
+ return Task.spawn(function* () {
+ let requestedUrl = TESTROOT2 + "enabled.html";
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl);
+ gBrowser.loadURI(TESTROOT2 + "enabled.html");
+ yield loadedPromise;
+
+ let progressPromise = waitForProgressNotification();
+ let notificationPromise = waitForNotification("addon-install-failed");
+ gBrowser.loadURI(TESTROOT + "corrupt.xpi");
+ yield progressPromise;
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "The add-on downloaded from this site could not be installed " +
+ "because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ yield removeTab();
+ });
+},
+
+function test_reload() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Unsigned XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ function testFail() {
+ ok(false, "Reloading should not have hidden the notification");
+ }
+ PopupNotifications.panel.addEventListener("popuphiding", testFail, false);
+ let requestedUrl = TESTROOT2 + "enabled.html";
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl);
+ gBrowser.loadURI(TESTROOT2 + "enabled.html");
+ yield loadedPromise;
+ PopupNotifications.panel.removeEventListener("popuphiding", testFail, false);
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_theme() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Theme XPI": "theme.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "Theme Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(result) {
+ resolve(result);
+ });
+ });
+ ok(addon.userDisabled, "Should be switching away from the default theme.");
+ // Undo the pending theme switch
+ addon.userDisabled = false;
+
+ addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(result) {
+ resolve(result);
+ });
+ });
+ isnot(addon, null, "Test theme will have been installed");
+ addon.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_renotifyBlocked() {
+ return Task.spawn(function* () {
+ let notificationPromise = waitForNotification("addon-install-blocked");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let closePromise = waitForNotificationClose();
+ // hide the panel (this simulates the user dismissing it)
+ panel.hidePopup();
+ yield closePromise;
+
+ info("Timeouts after this probably mean bug 589954 regressed");
+
+ yield new Promise(resolve => executeSoon(resolve));
+
+ notificationPromise = waitForNotification("addon-install-blocked");
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield notificationPromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 2, "Should be two pending installs");
+
+ closePromise = waitForNotificationClose();
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield closePromise;
+
+ installs = yield getInstalls();
+ is(installs.length, 0, "Should have cancelled the installs");
+ });
+},
+
+function test_renotifyInstalled() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ // Wait for the complete notification
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let closePromise = waitForNotificationClose();
+ // hide the panel (this simulates the user dismissing it)
+ panel.hidePopup();
+ yield closePromise;
+
+ // Install another
+ yield new Promise(resolve => executeSoon(resolve));
+
+ progressPromise = waitForProgressNotification();
+ dialogPromise = waitForInstallDialog();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ info("Timeouts after this probably mean bug 589954 regressed");
+
+ // Wait for the complete notification
+ notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending installs");
+ installs[0].cancel();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_cancel() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let notificationPromise = waitForNotification(PROGRESS_NOTIFICATION);
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "slowinstall.sjs?file=amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ // Close the notification
+ let anchor = document.getElementById("addons-notification-icon");
+ anchor.click();
+ // Reopen the notification
+ anchor.click();
+
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ let button = document.getElementById("addon-progress-cancel");
+
+ // Cancel the download
+ let install = notification.notification.options.installs[0];
+ let cancelledPromise = new Promise(resolve => {
+ install.addListener({
+ onDownloadCancelled: function() {
+ install.removeListener(this);
+ resolve();
+ }
+ });
+ });
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield cancelledPromise;
+
+ yield new Promise(resolve => executeSoon(resolve));
+
+ ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending install");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+},
+
+function test_failedSecurity() {
+ return Task.spawn(function* () {
+ Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
+ setupRedirect({
+ "Location": TESTROOT + "amosigned.xpi"
+ });
+
+ let notificationPromise = waitForNotification("addon-install-blocked");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "redirect.sjs?mode=redirect"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, SECUREROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ // Click on Allow
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+
+ // Notification should have changed to progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for it to fail
+ yield new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "addon-install-failed");
+ resolve();
+ }, "addon-install-failed", false);
+ });
+
+ // Allow the browser code to add the failure notification and then wait
+ // for the progress notification to dismiss itself
+ yield waitForSingleNotification();
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+
+ Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
+ yield removeTab();
+ });
+}
+];
+
+var gTestStart = null;
+
+var XPInstallObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+ info("Observed " + aTopic + " for " + installInfo.installs.length + " installs");
+ installInfo.installs.forEach(function(aInstall) {
+ info("Install of " + aInstall.sourceURI.spec + " was in state " + aInstall.state);
+ });
+ }
+};
+
+add_task(function* () {
+ requestLongerTimeout(4);
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+ Services.prefs.setBoolPref("extensions.strictCompatibility", true);
+ Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false);
+ Services.prefs.setIntPref("security.dialog_enable_delay", 0);
+
+ Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false);
+
+ registerCleanupFunction(function() {
+ // Make sure no more test parts run in case we were timed out
+ TESTS = [];
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ });
+
+ Services.prefs.clearUserPref("extensions.logging.enabled");
+ Services.prefs.clearUserPref("extensions.strictCompatibility");
+ Services.prefs.clearUserPref("extensions.install.requireSecureOrigin");
+ Services.prefs.clearUserPref("security.dialog_enable_delay");
+
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-complete");
+ });
+
+ for (let i = 0; i < TESTS.length; ++i) {
+ if (gTestStart)
+ info("Test part took " + (Date.now() - gTestStart) + "ms");
+
+ ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+ let installs = yield new Promise(resolve => {
+ AddonManager.getAllInstalls(function(aInstalls) {
+ resolve(aInstalls);
+ });
+ });
+
+ is(installs.length, 0, "Should be no active installs");
+ info("Running " + TESTS[i].name);
+ gTestStart = Date.now();
+ yield TESTS[i]();
+ }
+});
diff --git a/browser/base/content/test/general/browser_bug555224.js b/browser/base/content/test/general/browser_bug555224.js
new file mode 100644
index 000000000..d27bf0040
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug555224.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const TEST_PAGE = "/browser/browser/base/content/test/general/dummy_page.html";
+var gTestTab, gBgTab, gTestZoom;
+
+function testBackgroundLoad() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom");
+
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gBgTab);
+
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTestTab);
+ finish();
+ });
+}
+
+function testInitialZoom() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, 1, "initial zoom level should be 1");
+ FullZoom.enlarge();
+
+ gTestZoom = ZoomManager.zoom;
+ isnot(gTestZoom, 1, "zoom level should have changed");
+
+ gBgTab = gBrowser.addTab();
+ yield FullZoomHelper.load(gBgTab, "http://mochi.test:8888" + TEST_PAGE);
+ }).then(testBackgroundLoad, FullZoomHelper.failAndContinue(finish));
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTestTab = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTestTab);
+ yield FullZoomHelper.load(gTestTab, "http://example.org" + TEST_PAGE);
+ }).then(testInitialZoom, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug555767.js b/browser/base/content/test/general/browser_bug555767.js
new file mode 100644
index 000000000..bc774f7dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug555767.js
@@ -0,0 +1,54 @@
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ add_task(function* () {
+ let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ let tabSelected = false;
+
+ // Open the base tab
+ let baseTab = gBrowser.addTab(testURL);
+
+ // Wait for the tab to be fully loaded so matching happens correctly
+ yield promiseTabLoaded(baseTab);
+ if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
+ return;
+ baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let testTab = gBrowser.addTab();
+
+ // Select the testTab
+ gBrowser.selectedTab = testTab;
+
+ // Set the urlbar to include the moz-action
+ gURLBar.value = "moz-action:switchtab," + JSON.stringify({url: testURL});
+ // Focus the urlbar so we can press enter
+ gURLBar.focus();
+
+ // Functions for TabClose and TabSelect
+ function onTabClose(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
+ // Make sure we get the TabClose event for testTab
+ is(aEvent.originalTarget, testTab, "Got the TabClose event for the right tab");
+ // Confirm that we did select the tab
+ ok(tabSelected, "Confirming that the tab was selected");
+ gBrowser.removeTab(baseTab);
+ finish();
+ }
+ function onTabSelect(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ // Make sure we got the TabSelect event for baseTab
+ is(aEvent.originalTarget, baseTab, "Got the TabSelect event for the right tab");
+ // Confirm that the selected tab is in fact base tab
+ is(gBrowser.selectedTab, baseTab, "We've switched to the correct tab");
+ tabSelected = true;
+ }
+
+ // Add the TabClose, TabSelect event listeners before we press enter
+ gBrowser.tabContainer.addEventListener("TabClose", onTabClose, false);
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false);
+
+ // Press enter!
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ });
+
diff --git a/browser/base/content/test/general/browser_bug559991.js b/browser/base/content/test/general/browser_bug559991.js
new file mode 100644
index 000000000..b1516a8b4
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug559991.js
@@ -0,0 +1,42 @@
+var tab;
+
+function test() {
+
+ // ----------
+ // Test setup
+
+ waitForExplicitFinish();
+
+ gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true);
+ gPrefService.setBoolPref("browser.zoom.siteSpecific", true);
+
+ let uri = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+ Task.spawn(function* () {
+ tab = gBrowser.addTab();
+ yield FullZoomHelper.load(tab, uri);
+
+ // -------------------------------------------------------------------
+ // Test - Trigger a tab switch that should update the zoom level
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
+ ok(true, "applyPrefToSetting was called");
+ }).then(endTest, FullZoomHelper.failAndContinue(endTest));
+}
+
+// -------------
+// Test clean-up
+function endTest() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab);
+
+ tab = null;
+
+ if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
+ gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
+
+ if (gPrefService.prefHasUserValue("browser.zoom.siteSpecific"))
+ gPrefService.clearUserPref("browser.zoom.siteSpecific");
+
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug561636.js b/browser/base/content/test/general/browser_bug561636.js
new file mode 100644
index 000000000..69bc475c3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug561636.js
@@ -0,0 +1,370 @@
+var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ok(gInvalidFormPopup,
+ "The browser should have a popup to show when a form is invalid");
+
+function checkPopupShow()
+{
+ ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+ "[Test " + testId + "] The invalid form popup should be shown");
+}
+
+function checkPopupHide()
+{
+ ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open',
+ "[Test " + testId + "] The invalid form popup should not be shown");
+}
+
+var gObserver = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+ notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+ {
+ }
+};
+
+var testId = 0;
+
+function incrementTest()
+{
+ testId++;
+ info("Starting next part of test");
+}
+
+function getDocHeader()
+{
+ return "<html><head><meta charset='utf-8'></head><body>" + getEmptyFrame();
+}
+
+function getDocFooter()
+{
+ return "</body></html>";
+}
+
+function getEmptyFrame()
+{
+ return "<iframe style='width:100px; height:30px; margin:3px; border:1px solid lightgray;' " +
+ "name='t' srcdoc=\"<html><head><meta charset='utf-8'></head><body>form target</body></html>\"></iframe>";
+}
+
+function* openNewTab(uri, background)
+{
+ let tab = gBrowser.addTab();
+ let browser = gBrowser.getBrowserForTab(tab);
+ if (!background) {
+ gBrowser.selectedTab = tab;
+ }
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(uri));
+ return browser;
+}
+
+function* clickChildElement(browser)
+{
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.document.getElementById('s').click();
+ });
+}
+
+function* blurChildElement(browser)
+{
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.document.getElementById('i').blur();
+ });
+}
+
+function* checkChildFocus(browser, message)
+{
+ yield ContentTask.spawn(browser, [message, testId], function* (args) {
+ let [msg, id] = args;
+ var focused = content.document.activeElement == content.document.getElementById('i');
+
+ var validMsg = true;
+ if (msg) {
+ validMsg = (msg == content.document.getElementById('i').validationMessage);
+ }
+
+ Assert.equal(focused, true, "Test " + id + " First invalid element should be focused");
+ Assert.equal(validMsg, true, "Test " + id + " The panel should show the message from validationMessage");
+ });
+}
+
+/**
+ * In this test, we check that no popup appears if the form is valid.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ yield clickChildElement(browser);
+
+ yield new Promise((resolve, reject) => {
+ // XXXndeakin This isn't really going to work when the content is another process
+ executeSoon(function() {
+ checkPopupHide();
+ resolve();
+ });
+ });
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the invalid element is focused and a popup appears.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the first invalid element is focused and a popup appears.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input><input id='i' required><input required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, we hide the popup by interacting with the
+ * invalid element if the element becomes valid.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+ EventUtils.synthesizeKey("a", {});
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, we don't hide the popup by interacting with the
+ * invalid element if the element is still invalid.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input type='email' id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ yield new Promise((resolve, reject) => {
+ EventUtils.synthesizeKey("a", {});
+ executeSoon(function() {
+ checkPopupShow();
+ resolve();
+ })
+ });
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that we can hide the popup by blurring the invalid
+ * element.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+ yield blurChildElement(browser);
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that we can hide the popup by pressing TAB.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that the popup will hide if we move to another tab.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser1 = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser1);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser1, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+
+ let browser2 = yield openNewTab("data:text/html,<html></html>");
+ yield popupHiddenPromise;
+
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser1));
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser2));
+});
+
+/**
+ * In this test, we check that nothing happen if the invalid form is
+ * submitted in a background tab.
+ */
+add_task(function* ()
+{
+ // Observers don't propagate currently across processes. We may add support for this in the
+ // future via the addon compat layer.
+ if (gMultiProcessBrowser) {
+ return;
+ }
+
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri, true);
+ isnot(gBrowser.selectedBrowser, browser, "This tab should have been loaded in background");
+
+ let notifierPromise = new Promise((resolve, reject) => {
+ gObserver.notifyInvalidSubmit = function() {
+ executeSoon(function() {
+ checkPopupHide();
+
+ // Clean-up
+ Services.obs.removeObserver(gObserver, "invalidformsubmit");
+ gObserver.notifyInvalidSubmit = function () {};
+ resolve();
+ });
+ };
+
+ Services.obs.addObserver(gObserver, "invalidformsubmit", false);
+
+ executeSoon(function () {
+ browser.contentDocument.getElementById('s').click();
+ });
+ });
+
+ yield notifierPromise;
+
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser));
+});
+
+/**
+ * In this test, we check that the author defined error message is shown.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input x-moz-errormessage='foo' required id='i'><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ is(gInvalidFormPopup.firstChild.textContent, "foo",
+ "The panel should show the author defined error message");
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that the message is correctly updated when it changes.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input type='email' required id='i'><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let inputPromise = promiseWaitForEvent(gBrowser.contentDocument.getElementById('i'), "input");
+ EventUtils.synthesizeKey('f', {});
+ yield inputPromise;
+
+ // Now, the element suffers from another error, the message should have
+ // been updated.
+ yield new Promise((resolve, reject) => {
+ // XXXndeakin This isn't really going to work when the content is another process
+ executeSoon(function() {
+ checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+ resolve();
+ });
+ });
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug563588.js b/browser/base/content/test/general/browser_bug563588.js
new file mode 100644
index 000000000..a1774fb7e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug563588.js
@@ -0,0 +1,30 @@
+function press(key, expectedPos) {
+ var originalSelectedTab = gBrowser.selectedTab;
+ EventUtils.synthesizeKey("VK_" + key.toUpperCase(), { accelKey: true });
+ is(gBrowser.selectedTab, originalSelectedTab,
+ "accel+" + key + " doesn't change which tab is selected");
+ is(gBrowser.tabContainer.selectedIndex, expectedPos,
+ "accel+" + key + " moves the tab to the expected position");
+ is(document.activeElement, gBrowser.selectedTab,
+ "accel+" + key + " leaves the selected tab focused");
+}
+
+function test() {
+ gBrowser.addTab();
+ gBrowser.addTab();
+ is(gBrowser.tabs.length, 3, "got three tabs");
+ is(gBrowser.tabs[0], gBrowser.selectedTab, "first tab is selected");
+
+ gBrowser.selectedTab.focus();
+ is(document.activeElement, gBrowser.selectedTab, "selected tab is focused");
+
+ press("right", 1);
+ press("down", 2);
+ press("left", 1);
+ press("up", 0);
+ press("end", 2);
+ press("home", 0);
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/general/browser_bug565575.js b/browser/base/content/test/general/browser_bug565575.js
new file mode 100644
index 000000000..3555a2e7f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug565575.js
@@ -0,0 +1,14 @@
+add_task(function* () {
+ gBrowser.selectedBrowser.focus();
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => BrowserOpenTab(), false);
+ ok(gURLBar.focused, "location bar is focused for a new tab");
+
+ yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]);
+ ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab");
+
+ yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]);
+ ok(gURLBar.focused, "location bar is re-focused when selecting the new tab");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug567306.js b/browser/base/content/test/general/browser_bug567306.js
new file mode 100644
index 000000000..742ff6726
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug567306.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var {Ci: interfaces, Cc: classes} = Components;
+
+var Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+var HasFindClipboard = Clipboard.supportsFindClipboard();
+
+add_task(function* () {
+ let newwindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let selectedBrowser = newwindow.gBrowser.selectedBrowser;
+ yield new Promise((resolve, reject) => {
+ selectedBrowser.addEventListener("pageshow", function pageshowListener() {
+ if (selectedBrowser.currentURI.spec == "about:blank")
+ return;
+
+ selectedBrowser.removeEventListener("pageshow", pageshowListener, true);
+ ok(true, "pageshow listener called: " + newwindow.content.location);
+ resolve();
+ }, true);
+ selectedBrowser.loadURI("data:text/html,<h1 id='h1'>Select Me</h1>");
+ });
+
+ yield SimpleTest.promiseFocus(newwindow);
+
+ ok(!newwindow.gFindBarInitialized, "find bar is not yet initialized");
+ let findBar = newwindow.gFindBar;
+
+ yield ContentTask.spawn(selectedBrowser, { }, function* () {
+ let elt = content.document.getElementById("h1");
+ let selection = content.getSelection();
+ let range = content.document.createRange();
+ range.setStart(elt, 0);
+ range.setEnd(elt, 1);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ });
+
+ yield findBar.onFindCommand();
+
+ // When the OS supports the Find Clipboard (OSX), the find field value is
+ // persisted across Fx sessions, thus not useful to test.
+ if (!HasFindClipboard)
+ is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
+ findBar.close();
+ yield promiseWindowClosed(newwindow);
+});
+
diff --git a/browser/base/content/test/general/browser_bug575561.js b/browser/base/content/test/general/browser_bug575561.js
new file mode 100644
index 000000000..b6d17a447
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug575561.js
@@ -0,0 +1,97 @@
+requestLongerTimeout(2);
+
+const TEST_URL = "http://example.com/browser/browser/base/content/test/general/app_bug575561.html";
+
+add_task(function*() {
+ SimpleTest.requestCompleteLog();
+
+ // Pinned: Link to the same domain should not open a new tab
+ // Tests link to http://example.com/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(0, true, false);
+ // Pinned: Link to a different subdomain should open a new tab
+ // Tests link to http://test1.example.com/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(1, true, true);
+
+ // Pinned: Link to a different domain should open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(2, true, true);
+
+ // Not Pinned: Link to a different domain should not open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(2, false, false);
+
+ // Pinned: Targetted link should open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html with target="foo"
+ yield testLink(3, true, true);
+
+ // Pinned: Link in a subframe should not open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html in subframe
+ yield testLink(0, true, false, true);
+
+ // Pinned: Link to the same domain (with www prefix) should not open a new tab
+ // Tests link to http://www.example.com/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(4, true, false);
+
+ // Pinned: Link to a data: URI should not open a new tab
+ // Tests link to data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>
+ yield testLink(5, true, false);
+
+ // Pinned: Link to an about: URI should not open a new tab
+ // Tests link to about:logo
+ yield testLink(function(doc) {
+ let link = doc.createElement("a");
+ link.textContent = "Link to Mozilla";
+ link.href = "about:logo";
+ doc.body.appendChild(link);
+ return link;
+ }, true, false, false, "about:robots");
+});
+
+var waitForPageLoad = Task.async(function*(browser, linkLocation) {
+ yield waitForDocLoadComplete();
+
+ is(browser.contentDocument.location.href, linkLocation, "Link should not open in a new tab");
+});
+
+var waitForTabOpen = Task.async(function*() {
+ let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
+ ok(true, "Link should open a new tab");
+
+ yield waitForDocLoadComplete(event.target.linkedBrowser);
+ yield Promise.resolve();
+
+ gBrowser.removeCurrentTab();
+});
+
+var testLink = Task.async(function*(aLinkIndexOrFunction, pinTab, expectNewTab, testSubFrame, aURL = TEST_URL) {
+ let appTab = gBrowser.addTab(aURL, {skipAnimation: true});
+ if (pinTab)
+ gBrowser.pinTab(appTab);
+ gBrowser.selectedTab = appTab;
+
+ yield waitForDocLoadComplete();
+
+ let browser = appTab.linkedBrowser;
+ if (testSubFrame)
+ browser = browser.contentDocument.querySelector("iframe");
+
+ let link;
+ if (typeof aLinkIndexOrFunction == "function") {
+ link = aLinkIndexOrFunction(browser.contentDocument);
+ } else {
+ link = browser.contentDocument.querySelectorAll("a")[aLinkIndexOrFunction];
+ }
+
+ let promise;
+ if (expectNewTab)
+ promise = waitForTabOpen();
+ else
+ promise = waitForPageLoad(browser, link.href);
+
+ info("Clicking " + link.textContent);
+ link.click();
+
+ yield promise;
+
+ gBrowser.removeTab(appTab);
+});
diff --git a/browser/base/content/test/general/browser_bug575830.js b/browser/base/content/test/general/browser_bug575830.js
new file mode 100644
index 000000000..5393c08d7
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug575830.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+function test() {
+ let tab1, tab2;
+ const TEST_IMAGE = "http://example.org/browser/browser/base/content/test/general/moz.png";
+
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ tab1 = gBrowser.addTab();
+ tab2 = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.load(tab1, TEST_IMAGE);
+
+ is(ZoomManager.zoom, 1, "initial zoom level for first should be 1");
+
+ FullZoom.enlarge();
+ let zoom = ZoomManager.zoom;
+ isnot(zoom, 1, "zoom level should have changed");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
+ is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed");
+
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug577121.js b/browser/base/content/test/general/browser_bug577121.js
new file mode 100644
index 000000000..5ebfdc115
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug577121.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ Services.prefs.setBoolPref("browser.tabs.animate", false);
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.tabs.animate");
+ });
+
+ // Open 2 other tabs, and pin the second one. Like that, the initial tab
+ // should get closed.
+ let testTab1 = gBrowser.addTab();
+ let testTab2 = gBrowser.addTab();
+ gBrowser.pinTab(testTab2);
+
+ // Now execute "Close other Tabs" on the first manually opened tab (tab1).
+ // -> tab2 ist pinned, tab1 should remain open and the initial tab should
+ // get closed.
+ gBrowser.removeAllTabsBut(testTab1);
+
+ is(gBrowser.tabs.length, 2, "there are two remaining tabs open");
+ is(gBrowser.tabs[0], testTab2, "pinned tab2 stayed open");
+ is(gBrowser.tabs[1], testTab1, "tab1 stayed open");
+
+ // Cleanup. Close only one tab because we need an opened tab at the end of
+ // the test.
+ gBrowser.removeTab(testTab2);
+}
diff --git a/browser/base/content/test/general/browser_bug578534.js b/browser/base/content/test/general/browser_bug578534.js
new file mode 100644
index 000000000..0d61cca76
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug578534.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test() {
+ let uriString = "http://example.com/";
+ let cookieBehavior = "network.cookie.cookieBehavior";
+ let uriObj = Services.io.newURI(uriString, null, null)
+ let cp = Components.classes["@mozilla.org/cookie/permission;1"]
+ .getService(Components.interfaces.nsICookiePermission);
+
+ yield SpecialPowers.pushPrefEnv({ set: [[ cookieBehavior, 2 ]] });
+ cp.setAccess(uriObj, cp.ACCESS_ALLOW);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: uriString }, function* (browser) {
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.navigator.cookieEnabled, true,
+ "navigator.cookieEnabled should be true");
+ });
+ });
+
+ cp.setAccess(uriObj, cp.ACCESS_DEFAULT);
+});
diff --git a/browser/base/content/test/general/browser_bug579872.js b/browser/base/content/test/general/browser_bug579872.js
new file mode 100644
index 000000000..bc10ca0c8
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug579872.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let newTab = gBrowser.addTab();
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart);
+
+ function mainPart() {
+ gBrowser.pinTab(newTab);
+ gBrowser.selectedTab = newTab;
+
+ openUILinkIn("javascript:var x=0;", "current");
+ is(gBrowser.tabs.length, 2, "Should open in current tab");
+
+ openUILinkIn("http://example.com/1", "current");
+ is(gBrowser.tabs.length, 2, "Should open in current tab");
+
+ openUILinkIn("http://example.org/", "current");
+ is(gBrowser.tabs.length, 3, "Should open in new tab");
+
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
+ finish();
+ }
+ newTab.linkedBrowser.loadURI("http://example.com");
+}
diff --git a/browser/base/content/test/general/browser_bug580638.js b/browser/base/content/test/general/browser_bug580638.js
new file mode 100644
index 000000000..66defafe3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug580638.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ function testState(aPinned) {
+ function elemAttr(id, attr) {
+ return document.getElementById(id).getAttribute(attr);
+ }
+
+ if (aPinned) {
+ is(elemAttr("key_close", "disabled"), "true",
+ "key_close should be disabled when a pinned-tab is selected");
+ is(elemAttr("menu_close", "key"), "",
+ "menu_close shouldn't have a key set when a pinned is selected");
+ }
+ else {
+ is(elemAttr("key_close", "disabled"), "",
+ "key_closed shouldn't have disabled state set when a non-pinned tab is selected");
+ is(elemAttr("menu_close", "key"), "key_close",
+ "menu_close should have key_close set as its key when a non-pinned tab is selected");
+ }
+ }
+
+ let lastSelectedTab = gBrowser.selectedTab;
+ ok(!lastSelectedTab.pinned, "We should have started with a regular tab selected");
+
+ testState(false);
+
+ let pinnedTab = gBrowser.addTab("about:blank");
+ gBrowser.pinTab(pinnedTab);
+
+ // Just pinning the tab shouldn't change the key state.
+ testState(false);
+
+ // Test updating key state after selecting a tab.
+ gBrowser.selectedTab = pinnedTab;
+ testState(true);
+
+ gBrowser.selectedTab = lastSelectedTab;
+ testState(false);
+
+ gBrowser.selectedTab = pinnedTab;
+ testState(true);
+
+ // Test updating the key state after un/pinning the tab.
+ gBrowser.unpinTab(pinnedTab);
+ testState(false);
+
+ gBrowser.pinTab(pinnedTab);
+ testState(true);
+
+ // Test updating the key state after removing the tab.
+ gBrowser.removeTab(pinnedTab);
+ testState(false);
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug580956.js b/browser/base/content/test/general/browser_bug580956.js
new file mode 100644
index 000000000..b8e7bc20b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug580956.js
@@ -0,0 +1,26 @@
+function numClosedTabs() {
+ return SessionStore.getClosedTabCount(window);
+}
+
+function isUndoCloseEnabled() {
+ updateTabContextMenu();
+ return !document.getElementById("context_undoCloseTab").disabled;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", 0);
+ gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+ is(numClosedTabs(), 0, "There should be 0 closed tabs.");
+ ok(!isUndoCloseEnabled(), "Undo Close Tab should be disabled.");
+
+ var tab = gBrowser.addTab("http://mochi.test:8888/");
+ var browser = gBrowser.getBrowserForTab(tab);
+ BrowserTestUtils.browserLoaded(browser).then(() => {
+ BrowserTestUtils.removeTab(tab).then(() => {
+ ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled.");
+ finish();
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug581242.js b/browser/base/content/test/general/browser_bug581242.js
new file mode 100644
index 000000000..668c0cd41
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug581242.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Create a new tab and load about:addons
+ let blanktab = gBrowser.addTab();
+ gBrowser.selectedTab = blanktab;
+ BrowserOpenAddonsMgr();
+
+ is(blanktab, gBrowser.selectedTab, "Current tab should be blank tab");
+ // Verify that about:addons loads
+ waitForExplicitFinish();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ let browser = blanktab.linkedBrowser;
+ is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
+ gBrowser.removeTab(blanktab);
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug581253.js b/browser/base/content/test/general/browser_bug581253.js
new file mode 100644
index 000000000..0c537c3d3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug581253.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testURL = "data:text/plain,nothing but plain text";
+var testTag = "581253_tag";
+var timerID = -1;
+
+function test() {
+ registerCleanupFunction(function() {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ if (timerID > 0) {
+ clearTimeout(timerID);
+ }
+ });
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ tab.linkedBrowser.addEventListener("load", (function(event) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let uri = makeURI(testURL);
+ let bmTxn =
+ new PlacesCreateBookmarkTransaction(uri,
+ PlacesUtils.unfiledBookmarksFolderId,
+ -1, "", null, []);
+ PlacesUtils.transactionManager.doTransaction(bmTxn);
+
+ ok(PlacesUtils.bookmarks.isBookmarked(uri), "the test url is bookmarked");
+ waitForStarChange(true, onStarred);
+ }), true);
+
+ content.location = testURL;
+}
+
+function waitForStarChange(aValue, aCallback) {
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(waitForStarChange, 50, aValue, aCallback);
+ return;
+ }
+ aCallback();
+}
+
+function onStarred() {
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED,
+ "star button indicates that the page is bookmarked");
+
+ let uri = makeURI(testURL);
+ let tagTxn = new PlacesTagURITransaction(uri, [testTag]);
+ PlacesUtils.transactionManager.doTransaction(tagTxn);
+
+ StarUI.panel.addEventListener("popupshown", onPanelShown, false);
+ BookmarkingUI.star.click();
+}
+
+function onPanelShown(aEvent) {
+ if (aEvent.target == StarUI.panel) {
+ StarUI.panel.removeEventListener("popupshown", arguments.callee, false);
+ let tagsField = document.getElementById("editBMPanel_tagsField");
+ ok(tagsField.value == testTag, "tags field value was set");
+ tagsField.focus();
+
+ StarUI.panel.addEventListener("popuphidden", onPanelHidden, false);
+ let removeButton = document.getElementById("editBookmarkPanelRemoveButton");
+ removeButton.click();
+ }
+}
+
+function onPanelHidden(aEvent) {
+ if (aEvent.target == StarUI.panel) {
+ StarUI.panel.removeEventListener("popuphidden", arguments.callee, false);
+
+ executeSoon(function() {
+ ok(!PlacesUtils.bookmarks.isBookmarked(makeURI(testURL)),
+ "the bookmark for the test url has been removed");
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_UNSTARRED,
+ "star button indicates that the bookmark has been removed");
+ gBrowser.removeCurrentTab();
+ PlacesTestUtils.clearHistory().then(finish);
+ });
+ }
+}
diff --git a/browser/base/content/test/general/browser_bug585558.js b/browser/base/content/test/general/browser_bug585558.js
new file mode 100644
index 000000000..bae832b4d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug585558.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var tabs = [];
+
+function addTab(aURL) {
+ tabs.push(gBrowser.addTab(aURL, {skipAnimation: true}));
+}
+
+function testAttrib(elem, attrib, attribValue, msg) {
+ is(elem.hasAttribute(attrib), attribValue, msg);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add several new tabs in sequence, hiding some, to ensure that the
+ // correct attributes get set
+
+ addTab("http://mochi.test:8888/#0");
+ addTab("http://mochi.test:8888/#1");
+ addTab("http://mochi.test:8888/#2");
+ addTab("http://mochi.test:8888/#3");
+
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[4], "last-visible-tab", true,
+ "Fifth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
+ testAttrib(gBrowser.tabs[0], "afterselected-visible", false,
+ "First tab not marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ gBrowser.hideTab(gBrowser.tabs[1]);
+ executeSoon(test_hideSecond);
+}
+
+function test_hideSecond() {
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ gBrowser.showTab(gBrowser.tabs[1])
+ executeSoon(test_showSecond);
+}
+
+function test_showSecond() {
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", false,
+ "Third tab not marked as afterselected-visible!");
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ gBrowser.hideTab(gBrowser.tabs[0]);
+ executeSoon(test_hideFirst);
+}
+
+function test_hideFirst() {
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", false,
+ "Hidden first tab not marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[1], "first-visible-tab", true,
+ "Second tab marked first-visible-tab!");
+ gBrowser.showTab(gBrowser.tabs[0]);
+ executeSoon(test_showFirst);
+}
+
+function test_showFirst() {
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ gBrowser.selectedTab = gBrowser.tabs[2];
+ testAttrib(gBrowser.tabs[3], "afterselected-visible", true,
+ "Fourth tab marked afterselected-visible!");
+
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+ executeSoon(test_movedLower);
+}
+
+function test_movedLower() {
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ test_hoverOne();
+}
+
+function test_hoverOne() {
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[4], { type: "mousemove" });
+ testAttrib(gBrowser.tabs[3], "beforehovered", true, "Fourth tab marked beforehovered");
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[3], { type: "mousemove" });
+ testAttrib(gBrowser.tabs[2], "beforehovered", true, "Third tab marked beforehovered!");
+ testAttrib(gBrowser.tabs[2], "afterhovered", false, "Third tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[4], "afterhovered", true, "Fifth tab marked afterhovered!");
+ testAttrib(gBrowser.tabs[4], "beforehovered", false, "Fifth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "beforehovered", false, "First tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+ gBrowser.removeTab(tabs.pop());
+ executeSoon(test_hoverStatePersistence);
+}
+
+function test_hoverStatePersistence() {
+ // Test that the afterhovered and beforehovered attributes are still there when
+ // a tab is selected and then unselected again. See bug 856107.
+
+ function assertState() {
+ testAttrib(gBrowser.tabs[0], "beforehovered", true, "First tab still marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[2], "afterhovered", true, "Third tab still marked afterhovered!");
+ testAttrib(gBrowser.tabs[2], "beforehovered", false, "Third tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+ }
+
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[1], { type: "mousemove" });
+ assertState();
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ assertState();
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ assertState();
+ executeSoon(test_pinning);
+}
+
+function test_pinning() {
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
+ "Fourth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[3], "selected", true, "Fourth tab marked selected!");
+ testAttrib(gBrowser.tabs[3], "afterselected-visible", false,
+ "Fourth tab not marked afterselected-visible!");
+ // Causes gBrowser.tabs to change indices
+ gBrowser.pinTab(gBrowser.tabs[3]);
+ testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
+ "Fourth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ test_cleanUp();
+}
+
+function test_cleanUp() {
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug585785.js b/browser/base/content/test/general/browser_bug585785.js
new file mode 100644
index 000000000..4f9045231
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug585785.js
@@ -0,0 +1,35 @@
+var tab;
+
+function test() {
+ waitForExplicitFinish();
+
+ tab = gBrowser.addTab();
+ isnot(tab.getAttribute("fadein"), "true", "newly opened tab is yet to fade in");
+
+ // Try to remove the tab right before the opening animation's first frame
+ window.requestAnimationFrame(checkAnimationState);
+}
+
+function checkAnimationState() {
+ is(tab.getAttribute("fadein"), "true", "tab opening animation initiated");
+
+ info(window.getComputedStyle(tab).maxWidth);
+ gBrowser.removeTab(tab, { animate: true });
+ if (!tab.parentNode) {
+ ok(true, "tab removed synchronously since the opening animation hasn't moved yet");
+ finish();
+ return;
+ }
+
+ info("tab didn't close immediately, so the tab opening animation must have started moving");
+ info("waiting for the tab to close asynchronously");
+ tab.addEventListener("transitionend", function (event) {
+ if (event.propertyName == "max-width") {
+ tab.removeEventListener("transitionend", arguments.callee, false);
+ executeSoon(function () {
+ ok(!tab.parentNode, "tab removed asynchronously");
+ finish();
+ });
+ }
+ }, false);
+}
diff --git a/browser/base/content/test/general/browser_bug585830.js b/browser/base/content/test/general/browser_bug585830.js
new file mode 100644
index 000000000..6d3adf198
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug585830.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let tab1 = gBrowser.selectedTab;
+ let tab2 = gBrowser.addTab("about:blank", {skipAnimation: true});
+ gBrowser.addTab();
+ gBrowser.selectedTab = tab2;
+
+ gBrowser.removeCurrentTab({animate: true});
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, tab1, "First tab should be selected");
+ gBrowser.removeTab(tab2);
+
+ // test for "null has no properties" fix. See Bug 585830 Comment 13
+ gBrowser.removeCurrentTab({animate: true});
+ try {
+ gBrowser.tabContainer.advanceSelectedTab(-1, false);
+ } catch (err) {
+ ok(false, "Shouldn't throw");
+ }
+
+ gBrowser.removeTab(tab1);
+}
diff --git a/browser/base/content/test/general/browser_bug590206.js b/browser/base/content/test/general/browser_bug590206.js
new file mode 100644
index 000000000..f73d144e9
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug590206.js
@@ -0,0 +1,163 @@
+/*
+ * Test the identity mode UI for a variety of page types
+ */
+
+"use strict";
+
+const DUMMY = "browser/browser/base/content/test/general/dummy_page.html";
+
+function loadNewTab(url) {
+ return BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+}
+
+function getIdentityMode() {
+ return document.getElementById("identity-box").className;
+}
+
+function getConnectionState() {
+ gIdentityHandler.refreshIdentityPopup();
+ return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+// This test is slow on Linux debug e10s
+requestLongerTimeout(2);
+
+add_task(function* test_webpage() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("http://example.com/" + DUMMY);
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_blank() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("about:blank");
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_chrome() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("chrome://mozapps/content/extensions/extensions.xul");
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_https() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("https://example.com/" + DUMMY);
+ is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_addons() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("about:addons");
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_file() {
+ let oldTab = gBrowser.selectedTab;
+ let fileURI = getTestFilePath("");
+
+ let newTab = yield loadNewTab(fileURI);
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_resource_uri() {
+ let oldTab = gBrowser.selectedTab;
+ let dataURI = "resource://gre/modules/Services.jsm";
+
+ let newTab = yield loadNewTab(dataURI);
+
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_data_uri() {
+ let oldTab = gBrowser.selectedTab;
+ let dataURI = "data:text/html,hi"
+
+ let newTab = yield loadNewTab(dataURI);
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_about_uri() {
+ let oldTab = gBrowser.selectedTab;
+ let aboutURI = "about:robots"
+
+ let newTab = yield loadNewTab(aboutURI);
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
diff --git a/browser/base/content/test/general/browser_bug592338.js b/browser/base/content/test/general/browser_bug592338.js
new file mode 100644
index 000000000..ca9cc361a
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug592338.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+
+var tempScope = {};
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
+var LightweightThemeManager = tempScope.LightweightThemeManager;
+
+function wait_for_notification(aCallback) {
+ PopupNotifications.panel.addEventListener("popupshown", function() {
+ PopupNotifications.panel.removeEventListener("popupshown", arguments.callee, false);
+ aCallback(PopupNotifications.panel);
+ }, false);
+}
+
+var TESTS = [
+function test_install_http() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("http://example.org/browser/browser/base/content/test/general/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ executeSoon(function() {
+ BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser);
+
+ is(LightweightThemeManager.currentTheme, null, "Should not have installed the test theme");
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ pm.remove(makeURI("http://example.org/"), "install");
+
+ runNextTest();
+ });
+ }, false);
+},
+
+function test_install_lwtheme() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ var pm = Services.perms;
+ pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("https://example.com/browser/browser/base/content/test/general/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser);
+ let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
+ waitForCondition(
+ () => notificationBox.getNotificationWithValue("lwtheme-install-notification"),
+ () => {
+ is(LightweightThemeManager.currentTheme.id, "test", "Should have installed the test theme");
+
+ LightweightThemeManager.currentTheme = null;
+ gBrowser.removeTab(gBrowser.selectedTab);
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+
+ runNextTest();
+ }
+ );
+ }, false);
+},
+
+function test_lwtheme_switch_theme() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.userDisabled = false;
+ ok(aAddon.isActive, "Theme should have immediately enabled");
+ Services.prefs.setBoolPref("extensions.dss.enabled", false);
+
+ var pm = Services.perms;
+ pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("https://example.com/browser/browser/base/content/test/general/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ executeSoon(function() {
+ wait_for_notification(function(aPanel) {
+ is(LightweightThemeManager.currentTheme, null, "Should not have installed the test lwtheme");
+ ok(aAddon.isActive, "Test theme should still be active");
+
+ let notification = aPanel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+
+ ok(aAddon.userDisabled, "Should be waiting to disable the test theme");
+ aAddon.userDisabled = false;
+ Services.prefs.setBoolPref("extensions.dss.enabled", true);
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ Services.perms.remove(makeURI("http://example.com"), "install");
+
+ runNextTest();
+ });
+ BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser);
+ });
+ }, false);
+ });
+}
+];
+
+function runNextTest() {
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 0, "Should be no active installs");
+
+ if (TESTS.length == 0) {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.uninstall();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", false);
+ Services.prefs.setBoolPref("extensions.dss.enabled", false);
+
+ finish();
+ });
+ return;
+ }
+
+ info("Running " + TESTS[0].name);
+ TESTS.shift()();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+
+ AddonManager.getInstallForURL(TESTROOT + "theme.xpi", function(aInstall) {
+ aInstall.addListener({
+ onInstallEnded: function() {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ isnot(aAddon, null, "Should have installed the test theme.");
+
+ // In order to switch themes while the test is running we turn on dynamic
+ // theme switching. This means the test isn't exactly correct but should
+ // do some good
+ Services.prefs.setBoolPref("extensions.dss.enabled", true);
+
+ runNextTest();
+ });
+ }
+ });
+
+ aInstall.install();
+ }, "application/x-xpinstall");
+}
diff --git a/browser/base/content/test/general/browser_bug594131.js b/browser/base/content/test/general/browser_bug594131.js
new file mode 100644
index 000000000..ce09026ac
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug594131.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let newTab = gBrowser.addTab("http://example.com");
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart);
+
+ function mainPart() {
+ gBrowser.pinTab(newTab);
+ gBrowser.selectedTab = newTab;
+
+ openUILinkIn("http://example.org/", "current", { inBackground: true });
+ isnot(gBrowser.selectedTab, newTab, "shouldn't load in background");
+
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
+ finish();
+ }
+}
diff --git a/browser/base/content/test/general/browser_bug595507.js b/browser/base/content/test/general/browser_bug595507.js
new file mode 100644
index 000000000..54ae42346
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug595507.js
@@ -0,0 +1,36 @@
+/**
+ * Make sure that the form validation error message shows even if the form is in an iframe.
+ */
+add_task(function* () {
+ let uri = "<iframe src=\"data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>\"</iframe>";
+
+ var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ ok(gInvalidFormPopup,
+ "The browser should have a popup to show when a form is invalid");
+
+ let tab = gBrowser.addTab();
+ let browser = gBrowser.getBrowserForTab(tab);
+ gBrowser.selectedTab = tab;
+
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(uri));
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.document.getElementsByTagName('iframe')[0]
+ .contentDocument.getElementById('s').click();
+ });
+ yield popupShownPromise;
+
+ yield ContentTask.spawn(browser, {}, function* () {
+ let childdoc = content.document.getElementsByTagName('iframe')[0].contentDocument;
+ Assert.equal(childdoc.activeElement, childdoc.getElementById("i"),
+ "First invalid element should be focused");
+ });
+
+ ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+ "The invalid form popup should be shown");
+
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_bug596687.js b/browser/base/content/test/general/browser_bug596687.js
new file mode 100644
index 000000000..5c2b4fbfe
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug596687.js
@@ -0,0 +1,25 @@
+add_task(function* test() {
+ var tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ var gotTabAttrModified = false;
+ var gotTabClose = false;
+
+ function onTabClose() {
+ gotTabClose = true;
+ tab.addEventListener("TabAttrModified", onTabAttrModified, false);
+ }
+
+ function onTabAttrModified() {
+ gotTabAttrModified = true;
+ }
+
+ tab.addEventListener("TabClose", onTabClose, false);
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ ok(gotTabClose, "should have got the TabClose event");
+ ok(!gotTabAttrModified, "shouldn't have got the TabAttrModified event after TabClose");
+
+ tab.removeEventListener("TabClose", onTabClose, false);
+ tab.removeEventListener("TabAttrModified", onTabAttrModified, false);
+});
diff --git a/browser/base/content/test/general/browser_bug597218.js b/browser/base/content/test/general/browser_bug597218.js
new file mode 100644
index 000000000..5f4ededc3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug597218.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // establish initial state
+ is(gBrowser.tabs.length, 1, "we start with one tab");
+
+ // create a tab
+ let tab = gBrowser.loadOneTab("about:blank");
+ ok(!tab.hidden, "tab starts out not hidden");
+ is(gBrowser.tabs.length, 2, "we now have two tabs");
+
+ // make sure .hidden is read-only
+ tab.hidden = true;
+ ok(!tab.hidden, "can't set .hidden directly");
+
+ // hide the tab
+ gBrowser.hideTab(tab);
+ ok(tab.hidden, "tab is hidden");
+
+ // now pin it and make sure it gets unhidden
+ gBrowser.pinTab(tab);
+ ok(tab.pinned, "tab was pinned");
+ ok(!tab.hidden, "tab was unhidden");
+
+ // try hiding it now that it's pinned; shouldn't be able to
+ gBrowser.hideTab(tab);
+ ok(!tab.hidden, "tab did not hide");
+
+ // clean up
+ gBrowser.removeTab(tab);
+ is(gBrowser.tabs.length, 1, "we finish with one tab");
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug609700.js b/browser/base/content/test/general/browser_bug609700.js
new file mode 100644
index 000000000..8b4f1ea91
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug609700.js
@@ -0,0 +1,20 @@
+function test() {
+ waitForExplicitFinish();
+
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ Services.ww.unregisterNotification(arguments.callee);
+
+ ok(true, "duplicateTabIn opened a new window");
+
+ whenDelayedStartupFinished(aSubject, function () {
+ executeSoon(function () {
+ aSubject.close();
+ finish();
+ });
+ }, false);
+ }
+ });
+
+ duplicateTabIn(gBrowser.selectedTab, "window");
+}
diff --git a/browser/base/content/test/general/browser_bug623893.js b/browser/base/content/test/general/browser_bug623893.js
new file mode 100644
index 000000000..fa6da1b22
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug623893.js
@@ -0,0 +1,37 @@
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab("data:text/plain;charset=utf-8,1", function* (browser) {
+ BrowserTestUtils.loadURI(browser, "data:text/plain;charset=utf-8,2");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.loadURI(browser, "data:text/plain;charset=utf-8,3");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield duplicate(0, "maintained the original index");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ yield duplicate(-1, "went back");
+ yield duplicate(1, "went forward");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
+
+function promiseGetIndex(browser) {
+ return ContentTask.spawn(browser, null, function() {
+ let shistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+ return shistory.index;
+ });
+}
+
+let duplicate = Task.async(function* (delta, msg, cb) {
+ var startIndex = yield promiseGetIndex(gBrowser.selectedBrowser);
+
+ duplicateTabIn(gBrowser.selectedTab, "tab", delta);
+
+ let tab = gBrowser.selectedTab;
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+
+ let endIndex = yield promiseGetIndex(gBrowser.selectedBrowser);
+ is(endIndex, startIndex + delta, msg);
+});
diff --git a/browser/base/content/test/general/browser_bug624734.js b/browser/base/content/test/general/browser_bug624734.js
new file mode 100644
index 000000000..d6fc7acbc
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug624734.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 624734 - Star UI has no tooltip until bookmarked page is visited
+
+function finishTest() {
+ is(BookmarkingUI.button.getAttribute("buttontooltiptext"),
+ BookmarkingUI._unstarredTooltip,
+ "Star icon should have the unstarred tooltip text");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING) {
+ waitForCondition(() => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING, finishTest, "BookmarkingUI was updating for too long");
+ } else {
+ finishTest();
+ }
+ });
+
+ tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/general/dummy_page.html");
+}
diff --git a/browser/base/content/test/general/browser_bug633691.js b/browser/base/content/test/general/browser_bug633691.js
new file mode 100644
index 000000000..28a8440ff
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug633691.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* test() {
+ const URL = "data:text/html,<iframe width='700' height='700'></iframe>";
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, function* (browser) {
+ yield ContentTask.spawn(browser,
+ { is_element_hidden_: is_element_hidden.toSource(),
+ is_hidden_: is_hidden.toSource() },
+ function* ({ is_element_hidden_, is_hidden_ }) {
+ let loadError =
+ ContentTaskUtils.waitForEvent(this, "AboutNetErrorLoad", false, null, true);
+ let iframe = content.document.querySelector("iframe");
+ iframe.src = "https://expired.example.com/";
+
+ yield loadError;
+
+ let is_hidden = eval(`(() => ${is_hidden_})()`);
+ let is_element_hidden = eval(`(() => ${is_element_hidden_})()`);
+ let doc = content.document.getElementsByTagName("iframe")[0].contentDocument;
+ let aP = doc.getElementById("badCertAdvancedPanel");
+ ok(aP, "Advanced content should exist");
+ void is_hidden; // Quiet eslint warnings (actual use under is_element_hidden)
+ is_element_hidden(aP, "Advanced content should not be visible by default")
+ });
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug647886.js b/browser/base/content/test/general/browser_bug647886.js
new file mode 100644
index 000000000..6c28c465c
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug647886.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.history.pushState({}, "2", "2.html");
+ });
+
+ var backButton = document.getElementById("back-button");
+ var rect = backButton.getBoundingClientRect();
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(backButton, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(backButton, {type: "mousedown"});
+ EventUtils.synthesizeMouse(backButton, rect.width / 2, rect.height, {type: "mouseup"});
+ let event = yield popupShownPromise;
+
+ ok(true, "history menu opened");
+
+ // Wait for the session data to be flushed before continuing the test
+ yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
+
+ is(event.target.children.length, 2, "Two history items");
+
+ let node = event.target.firstChild;
+ is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri");
+ is(node.getAttribute("index"), "1", "first item index");
+ is(node.getAttribute("historyindex"), "0", "first item historyindex");
+
+ node = event.target.lastChild;
+ is(node.getAttribute("uri"), "http://example.com/", "second item uri");
+ is(node.getAttribute("index"), "0", "second item index");
+ is(node.getAttribute("historyindex"), "-1", "second item historyindex");
+
+ event.target.hidePopup();
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_bug655584.js b/browser/base/content/test/general/browser_bug655584.js
new file mode 100644
index 000000000..b836e3173
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug655584.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 655584 - awesomebar suggestions don't update after tab is closed
+
+add_task(function* () {
+ var tab1 = gBrowser.addTab();
+ var tab2 = gBrowser.addTab();
+
+ // When urlbar in a new tab is focused, and a tab switch occurs,
+ // the urlbar popup should be closed
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ gURLBar.focus(); // focus the urlbar in the tab we will switch to
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ gURLBar.openPopup();
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup");
+
+ // cleanup
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug664672.js b/browser/base/content/test/general/browser_bug664672.js
new file mode 100644
index 000000000..2064f77d0
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug664672.js
@@ -0,0 +1,19 @@
+function test() {
+ waitForExplicitFinish();
+
+ var tab = gBrowser.addTab();
+
+ tab.addEventListener("TabClose", function () {
+ tab.removeEventListener("TabClose", arguments.callee, false);
+
+ ok(tab.linkedBrowser, "linkedBrowser should still exist during the TabClose event");
+
+ executeSoon(function () {
+ ok(!tab.linkedBrowser, "linkedBrowser should be gone after the TabClose event");
+
+ finish();
+ });
+ }, false);
+
+ gBrowser.removeTab(tab);
+}
diff --git a/browser/base/content/test/general/browser_bug676619.js b/browser/base/content/test/general/browser_bug676619.js
new file mode 100644
index 000000000..6b596481d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug676619.js
@@ -0,0 +1,124 @@
+function test () {
+ requestLongerTimeout(3);
+ waitForExplicitFinish();
+
+ var isHTTPS = false;
+
+ function loadListener() {
+ function testLocation(link, url, next) {
+ new TabOpenListener(url, function () {
+ gBrowser.removeTab(this.tab);
+ }, function () {
+ next();
+ });
+
+ ContentTask.spawn(gBrowser.selectedBrowser, link, contentLink => {
+ content.document.getElementById(contentLink).click();
+ });
+ }
+
+ function testLink(link, name, next) {
+ addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function (win) {
+ ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+ Assert.equal(content.document.getElementById("unload-flag").textContent,
+ "Okay", "beforeunload shouldn't have fired");
+ }).then(() => {
+ is(win.document.getElementById("location").value, name, "file name should match");
+ win.close();
+ next();
+ });
+ });
+
+ ContentTask.spawn(gBrowser.selectedBrowser, link, contentLink => {
+ content.document.getElementById(contentLink).click();
+ });
+ }
+
+ testLink("link1", "test.txt",
+ testLink.bind(null, "link2", "video.ogg",
+ testLink.bind(null, "link3", "just some video",
+ testLink.bind(null, "link4", "with-target.txt",
+ testLink.bind(null, "link5", "javascript.txt",
+ testLink.bind(null, "link6", "test.blob",
+ testLocation.bind(null, "link7", "http://example.com/",
+ function () {
+ if (isHTTPS) {
+ finish();
+ } else {
+ // same test again with https:
+ isHTTPS = true;
+ gBrowser.loadURI("https://example.com:443/browser/browser/base/content/test/general/download_page.html");
+ }
+ })))))));
+
+ }
+
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ loadListener();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(loadListener);
+ });
+
+ gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html");
+}
+
+
+function addWindowListener(aURL, aCallback) {
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ info("window opened, waiting for focus");
+ Services.wm.removeListener(this);
+
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ waitForFocus(function() {
+ is(domwindow.document.location.href, aURL, "should have seen the right window open");
+ aCallback(domwindow);
+ }, domwindow);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ });
+}
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+ this.url = url;
+ this.opencallback = opencallback;
+ this.closecallback = closecallback;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+ url: null,
+ opencallback: null,
+ closecallback: null,
+ tab: null,
+ browser: null,
+
+ handleEvent: function(event) {
+ if (event.type == "TabOpen") {
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ this.tab = event.originalTarget;
+ this.browser = this.tab.linkedBrowser;
+ BrowserTestUtils.browserLoaded(this.browser, false, this.url).then(() => {
+ this.tab.addEventListener("TabClose", this, false);
+ var url = this.browser.currentURI.spec;
+ is(url, this.url, "Should have opened the correct tab");
+ this.opencallback();
+ });
+ } else if (event.type == "TabClose") {
+ if (event.originalTarget != this.tab)
+ return;
+ this.tab.removeEventListener("TabClose", this, false);
+ this.opencallback = null;
+ this.tab = null;
+ this.browser = null;
+ // Let the window close complete
+ executeSoon(this.closecallback);
+ this.closecallback = null;
+ }
+ }
+};
diff --git a/browser/base/content/test/general/browser_bug678392-1.html b/browser/base/content/test/general/browser_bug678392-1.html
new file mode 100644
index 000000000..c3b235dd0
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug678392-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>bug678392 - 1</title>
+ </head>
+ <body>
+bug 678392 test page 1
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/base/content/test/general/browser_bug678392-2.html b/browser/base/content/test/general/browser_bug678392-2.html
new file mode 100644
index 000000000..9b18efcf7
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug678392-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>bug678392 - 2</title>
+ </head>
+ <body>
+bug 678392 test page 2
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/base/content/test/general/browser_bug678392.js b/browser/base/content/test/general/browser_bug678392.js
new file mode 100644
index 000000000..6aedeefdf
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug678392.js
@@ -0,0 +1,191 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var HTTPROOT = "http://example.com/browser/browser/base/content/test/general/";
+
+function maxSnapshotOverride() {
+ return 5;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ BrowserOpenTab();
+ let tab = gBrowser.selectedTab;
+ registerCleanupFunction(function () { gBrowser.removeTab(tab); });
+
+ ok(gHistorySwipeAnimation, "gHistorySwipeAnimation exists.");
+
+ if (!gHistorySwipeAnimation._isSupported()) {
+ is(gHistorySwipeAnimation.active, false, "History swipe animation is not " +
+ "active when not supported by the platform.");
+ finish();
+ return;
+ }
+
+ gHistorySwipeAnimation._getMaxSnapshots = maxSnapshotOverride;
+ gHistorySwipeAnimation.init();
+
+ is(gHistorySwipeAnimation.active, true, "History swipe animation support " +
+ "was successfully initialized when supported.");
+
+ cleanupArray();
+ load(gBrowser.selectedTab, HTTPROOT + "browser_bug678392-2.html", test0);
+}
+
+function load(aTab, aUrl, aCallback) {
+ aTab.linkedBrowser.addEventListener("load", function onload(aEvent) {
+ aEvent.currentTarget.removeEventListener("load", onload, true);
+ waitForFocus(aCallback, content);
+ }, true);
+ aTab.linkedBrowser.loadURI(aUrl);
+}
+
+function cleanupArray() {
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ while (arr.length > 0) {
+ delete arr[0].browser.snapshots[arr[0].index]; // delete actual snapshot
+ arr.splice(0, 1);
+ }
+}
+
+function testArrayCleanup() {
+ // Test cleanup of array of tracked snapshots.
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ is(arr.length, 0, "Snapshots were removed correctly from the array of " +
+ "tracked snapshots.");
+}
+
+function test0() {
+ // Test growing of array of tracked snapshots.
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ ok(gHistorySwipeAnimation._trackedSnapshots, "Array for snapshot " +
+ "tracking is initialized.");
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Snapshot array " +
+ "has correct length of 1 after loading one page.");
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Snapshot array " +
+ " has correct length of 2 after loading two pages.");
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Snapshot " +
+ "array has correct length of 3 after loading three pages.");
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Snapshot " +
+ "array has correct length of 4 after loading four pages.");
+ cleanupArray();
+ testArrayCleanup();
+ test1();
+ });
+ });
+ });
+ });
+}
+
+function verifyRefRemoved(aIndex, aBrowser) {
+ let wasFound = false;
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i].index == aIndex && arr[i].browser == aBrowser)
+ wasFound = true;
+ }
+ is(wasFound, false, "The reference that was previously removed was " +
+ "still found in the array of tracked snapshots.");
+}
+
+function test1() {
+ // Test presence of snpashots in per-tab array of snapshots and removal of
+ // individual snapshots (and corresponding references in the array of
+ // tracked snapshots).
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ var historyIndex = gBrowser.webNavigation.sessionHistory.index - 1;
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ let browser = gBrowser.selectedBrowser;
+ ok(browser.snapshots, "Array of snapshots exists in browser.");
+ ok(browser.snapshots[historyIndex], "First page exists in snapshot " +
+ "array.");
+ ok(browser.snapshots[historyIndex + 1], "Second page exists in " +
+ "snapshot array.");
+ ok(browser.snapshots[historyIndex + 2], "Third page exists in " +
+ "snapshot array.");
+ ok(browser.snapshots[historyIndex + 3], "Fourth page exists in " +
+ "snapshot array.");
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length of " +
+ "array of tracked snapshots is equal to 4 after loading four " +
+ "pages.");
+
+ // Test removal of reference in the middle of the array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 1,
+ browser);
+ verifyRefRemoved(historyIndex + 1, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Length of " +
+ "array of tracked snapshots is equal to 3 after removing one" +
+ "reference from the array with length 4.");
+
+ // Test removal of reference at end of array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 3,
+ browser);
+ verifyRefRemoved(historyIndex + 3, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " +
+ "array of tracked snapshots is equal to 2 after removing two" +
+ "references from the array with length 4.");
+
+ // Test removal of reference at head of array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex,
+ browser);
+ verifyRefRemoved(historyIndex, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Length of " +
+ "array of tracked snapshots is equal to 1 after removing three" +
+ "references from the array with length 4.");
+
+ cleanupArray();
+ test2();
+ });
+ });
+ });
+ });
+}
+
+function test2() {
+ // Test growing of snapshot array across tabs.
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " +
+ "snapshot array is equal to 2 after loading two pages");
+ let prevTab = tab;
+ tab = gBrowser.addTab("about:newtab");
+ gBrowser.selectedTab = tab;
+ load(tab, HTTPROOT + "browser_bug678392-2.html" /* initial page */,
+ function() {
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length " +
+ "of snapshot array is equal to 4 after loading two pages in " +
+ "two tabs each.");
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = prevTab;
+ cleanupArray();
+ test3();
+ });
+ });
+ });
+ });
+ });
+}
+
+function test3() {
+ // Test uninit of gHistorySwipeAnimation.
+ // This test MUST be the last one to execute.
+ gHistorySwipeAnimation.uninit();
+ is(gHistorySwipeAnimation.active, false, "History swipe animation support " +
+ "was successfully uninitialized");
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug710878.js b/browser/base/content/test/general/browser_bug710878.js
new file mode 100644
index 000000000..dd99d67cf
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug710878.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PAGE = "data:text/html;charset=utf-8,<a href='%23xxx'><span>word1 <span> word2 </span></span><span> word3</span></a>";
+
+/**
+ * Tests that we correctly compute the text for context menu
+ * selection of some content.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
+ "popupshown");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
+ "popuphidden");
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a", {
+ type: "contextmenu",
+ button: 2,
+ }, browser);
+
+ yield awaitPopupShown;
+
+ is(gContextMenu.linkTextStr, "word1 word2 word3",
+ "Text under link is correctly computed.");
+
+ contextMenu.hidePopup();
+ yield awaitPopupHidden;
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug719271.js b/browser/base/content/test/general/browser_bug719271.js
new file mode 100644
index 000000000..c3bb9cd26
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug719271.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html";
+const TEST_VIDEO = "http://example.org/browser/browser/base/content/test/general/video.ogg";
+
+var gTab1, gTab2, gLevel1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTab1 = gBrowser.addTab();
+ gTab2 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, TEST_PAGE);
+ yield FullZoomHelper.load(gTab2, TEST_VIDEO);
+ }).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab1() {
+ Task.spawn(function* () {
+ is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
+
+ // Reset zoom level if we run this test > 1 time in same browser session.
+ var level1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+ if (level1 > 1)
+ FullZoom.reduce();
+
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+
+ FullZoom.enlarge();
+ gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+
+ ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected");
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed");
+ }).then(zoomTab2, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab2() {
+ Task.spawn(function* () {
+ is(gBrowser.selectedTab, gTab2, "Tab 2 is selected");
+
+ FullZoom.reduce();
+ let level2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2));
+
+ ok(level2 < 1, "New zoom for tab 2 should be less than 1");
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected");
+ }).then(testNavigation, FullZoomHelper.failAndContinue(finish));
+}
+
+function testNavigation() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.load(gTab1, TEST_VIDEO);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when a video was loaded");
+ yield waitForNextTurn(); // trying to fix orange bug 806046
+ yield FullZoomHelper.navigate(FullZoomHelper.BACK);
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Zoom should be restored when a page is loaded");
+ yield waitForNextTurn(); // trying to fix orange bug 806046
+ yield FullZoomHelper.navigate(FullZoomHelper.FORWARD);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 again when navigating back to a video");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+function waitForNextTurn() {
+ let deferred = Promise.defer();
+ setTimeout(() => deferred.resolve(), 0);
+ return deferred.promise;
+}
+
+var finishTestStarted = false;
+function finishTest() {
+ Task.spawn(function* () {
+ ok(!finishTestStarted, "finishTest called more than once");
+ finishTestStarted = true;
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug724239.js b/browser/base/content/test/general/browser_bug724239.js
new file mode 100644
index 000000000..430751b91
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug724239.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (browser) {
+ BrowserTestUtils.loadURI(browser, "http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+ ok(!gBrowser.canGoBack, "about:newtab wasn't added to the session history");
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug734076.js b/browser/base/content/test/general/browser_bug734076.js
new file mode 100644
index 000000000..9de7d913f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug734076.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* ()
+{
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, null, false);
+
+ let browser = tab.linkedBrowser;
+ browser.stop(); // stop the about:blank load
+
+ let writeDomainURL = encodeURI("data:text/html,<script>document.write(document.domain);</script>");
+
+ let tests = [
+ {
+ name: "view background image",
+ url: "http://mochi.test:8888/",
+ element: "body",
+ go: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+ let contentBody = content.document.body;
+ contentBody.style.backgroundImage = "url('" + arg.writeDomainURL + "')";
+
+ return "context-viewbgimage";
+ });
+ },
+ verify: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) {
+ Assert.ok(!content.document.body.textContent,
+ "no domain was inherited for view background image");
+ });
+ }
+ },
+ {
+ name: "view image",
+ url: "http://mochi.test:8888/",
+ element: "img",
+ go: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+ let doc = content.document;
+ let img = doc.createElement("img");
+ img.height = 100;
+ img.width = 100;
+ img.setAttribute("src", arg.writeDomainURL);
+ doc.body.insertBefore(img, doc.body.firstChild);
+
+ return "context-viewimage";
+ });
+ },
+ verify: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) {
+ Assert.ok(!content.document.body.textContent,
+ "no domain was inherited for view image");
+ });
+ }
+ },
+ {
+ name: "show only this frame",
+ url: "http://mochi.test:8888/",
+ element: "iframe",
+ go: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+ let doc = content.document;
+ let iframe = doc.createElement("iframe");
+ iframe.setAttribute("src", arg.writeDomainURL);
+ doc.body.insertBefore(iframe, doc.body.firstChild);
+
+ // Wait for the iframe to load.
+ return new Promise(resolve => {
+ iframe.addEventListener("load", function onload() {
+ iframe.removeEventListener("load", onload, true);
+ resolve("context-showonlythisframe");
+ }, true);
+ });
+ });
+ },
+ verify: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) {
+ Assert.ok(!content.document.body.textContent,
+ "no domain was inherited for 'show only this frame'");
+ });
+ }
+ }
+ ];
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ for (let test of tests) {
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(test.url);
+ yield loadedPromise;
+
+ info("Run subtest " + test.name);
+ let commandToRun = yield test.go();
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse(test.element, 3, 3,
+ { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+ info("onImage: " + gContextMenu.onImage);
+ info("target: " + gContextMenu.target.tagName);
+
+ let loadedAfterCommandPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ document.getElementById(commandToRun).click();
+ yield loadedAfterCommandPromise;
+
+ yield test.verify();
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug735471.js b/browser/base/content/test/general/browser_bug735471.js
new file mode 100644
index 000000000..9afb52c4b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug735471.js
@@ -0,0 +1,23 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+function test() {
+ waitForExplicitFinish();
+ // Open a new tab.
+ whenNewTabLoaded(window, testPreferences);
+}
+
+function testPreferences() {
+ whenTabLoaded(gBrowser.selectedTab, function () {
+ is(content.location.href, "about:preferences", "Checking if the preferences tab was opened");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+
+ openPreferences();
+}
diff --git a/browser/base/content/test/general/browser_bug749738.js b/browser/base/content/test/general/browser_bug749738.js
new file mode 100644
index 000000000..7e805b799
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug749738.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DUMMY_PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ BrowserTestUtils.loadURI(tab.linkedBrowser, DUMMY_PAGE);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ gFindBar.onFindCommand();
+ EventUtils.sendString("Dummy");
+ gBrowser.removeTab(tab);
+
+ try {
+ gFindBar.close();
+ ok(true, "findbar.close should not throw an exception");
+ } catch (e) {
+ ok(false, "findbar.close threw exception: " + e);
+ }
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug763468_perwindowpb.js b/browser/base/content/test/general/browser_bug763468_perwindowpb.js
new file mode 100644
index 000000000..23cb14b8c
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug763468_perwindowpb.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* globals
+ waitForExplicitFinish, whenNewWindowLoaded, whenNewTabLoaded,
+ executeSoon, registerCleanupFunction, finish, is
+*/
+/* exported test */
+
+// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing
+function test() {
+ // initialization
+ waitForExplicitFinish();
+
+ let windowsToClose = [];
+ let newTabURL;
+ let mode;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ whenNewTabLoaded(aWindow, function() {
+ if (aIsPrivateMode) {
+ mode = "per window private browsing";
+ newTabURL = "about:privatebrowsing";
+ } else {
+ mode = "normal";
+ newTabURL = "about:newtab";
+ }
+
+ is(aWindow.gBrowser.currentURI.spec, newTabURL,
+ "URL of NewTab should be " + newTabURL + " in " + mode + " mode");
+
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aCallback();
+ });
+ }
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(() => aCallback(aWin));
+ });
+ }
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function() {
+ windowsToClose.forEach(function(aWin) {
+ aWin.close();
+ });
+ });
+
+ // test first when not on private mode
+ testOnWindow({}, function(aWin) {
+ doTest(false, aWin, function() {
+ // then test when on private mode
+ testOnWindow({private: true}, function(aWin2) {
+ doTest(true, aWin2, function() {
+ // then test again when not on private mode
+ testOnWindow({}, function(aWin3) {
+ doTest(false, aWin3, finish);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug767836_perwindowpb.js b/browser/base/content/test/general/browser_bug767836_perwindowpb.js
new file mode 100644
index 000000000..7f5d15e76
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug767836_perwindowpb.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+/* globals waitForExplicitFinish, executeSoon, finish, whenNewWindowLoaded, ok */
+/* globals is */
+/* exported test */
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+
+ let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
+ .getService(Components.interfaces.nsIAboutNewTabService);
+ let newTabURL;
+ let testURL = "http://example.com/";
+ let defaultURL = aboutNewTabService.newTabURL;
+ let mode;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ openNewTab(aWindow, function() {
+ if (aIsPrivateMode) {
+ mode = "per window private browsing";
+ newTabURL = "about:privatebrowsing";
+ } else {
+ mode = "normal";
+ newTabURL = "about:newtab";
+ }
+
+ // Check the new tab opened while in normal/private mode
+ is(aWindow.gBrowser.selectedBrowser.currentURI.spec, newTabURL,
+ "URL of NewTab should be " + newTabURL + " in " + mode + " mode");
+ // Set the custom newtab url
+ aboutNewTabService.newTabURL = testURL;
+ is(aboutNewTabService.newTabURL, testURL, "Custom newtab url is set");
+
+ // Open a newtab after setting the custom newtab url
+ openNewTab(aWindow, function() {
+ is(aWindow.gBrowser.selectedBrowser.currentURI.spec, testURL,
+ "URL of NewTab should be the custom url");
+
+ // Clear the custom url.
+ aboutNewTabService.resetNewTabURL();
+ is(aboutNewTabService.newTabURL, defaultURL, "No custom newtab url is set");
+
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aWindow.close();
+ aCallback();
+ });
+ });
+ }
+
+ function testOnWindow(aIsPrivate, aCallback) {
+ whenNewWindowLoaded({private: aIsPrivate}, function(win) {
+ executeSoon(() => aCallback(win));
+ });
+ }
+
+ // check whether any custom new tab url has been configured
+ ok(!aboutNewTabService.overridden, "No custom newtab url is set");
+
+ // test normal mode
+ testOnWindow(false, function(aWindow) {
+ doTest(false, aWindow, function() {
+ // test private mode
+ testOnWindow(true, function(aWindow2) {
+ doTest(true, aWindow2, function() {
+ finish();
+ });
+ });
+ });
+ });
+}
+
+function openNewTab(aWindow, aCallback) {
+ // Open a new tab
+ aWindow.BrowserOpenTab();
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ if (browser.contentDocument.readyState === "complete") {
+ executeSoon(aCallback);
+ return;
+ }
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug817947.js b/browser/base/content/test/general/browser_bug817947.js
new file mode 100644
index 000000000..3a76e36d3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug817947.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+const URL = "http://mochi.test:8888/browser/";
+const PREF = "browser.sessionstore.restore_on_demand";
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF);
+ });
+
+ preparePendingTab(function (aTab) {
+ let win = gBrowser.replaceTabWithWindow(aTab);
+
+ whenDelayedStartupFinished(win, function () {
+ let [tab] = win.gBrowser.tabs;
+
+ whenLoaded(tab.linkedBrowser, function () {
+ is(tab.linkedBrowser.currentURI.spec, URL, "correct url should be loaded");
+ ok(!tab.hasAttribute("pending"), "tab should not be pending");
+
+ win.close();
+ finish();
+ });
+ });
+ });
+}
+
+function preparePendingTab(aCallback) {
+ let tab = gBrowser.addTab(URL);
+
+ whenLoaded(tab.linkedBrowser, function () {
+ BrowserTestUtils.removeTab(tab).then(() => {
+ let [{state}] = JSON.parse(SessionStore.getClosedTabData(window));
+
+ tab = gBrowser.addTab("about:blank");
+ whenLoaded(tab.linkedBrowser, function () {
+ SessionStore.setTabState(tab, JSON.stringify(state));
+ ok(tab.hasAttribute("pending"), "tab should be pending");
+ aCallback(tab);
+ });
+ });
+ });
+}
+
+function whenLoaded(aElement, aCallback) {
+ aElement.addEventListener("load", function onLoad() {
+ aElement.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug822367.js b/browser/base/content/test/general/browser_bug822367.js
new file mode 100644
index 000000000..0d60c05cd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug822367.js
@@ -0,0 +1,187 @@
+/*
+ * User Override Mixed Content Block - Tests for Bug 822367
+ */
+
+
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+// We alternate for even and odd test cases to simulate different hosts
+const gHttpTestRoot = "https://example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "https://test1.example.com/browser/browser/base/content/test/general/";
+
+var gTestBrowser = null;
+
+add_task(function* test() {
+ yield SpecialPowers.pushPrefEnv({ set: [[ PREF_DISPLAY, true ],
+ [ PREF_ACTIVE, true ]] });
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ // Mixed Script Test
+ var url = gHttpTestRoot + "file_bug822367_1.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+// Mixed Script Test
+add_task(function* MixedTest1A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest1B() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "hello",
+ "Waited too long for mixed script to run in Test 1");
+ });
+});
+
+// Mixed Display Test - Doorhanger should not appear
+add_task(function* MixedTest2() {
+ var url = gHttpTestRoot2 + "file_bug822367_2.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false});
+});
+
+// Mixed Script and Display Test - User Override should cause both the script and the image to load.
+add_task(function* MixedTest3() {
+ var url = gHttpTestRoot + "file_bug822367_3.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest3A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest3B() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let p1 = ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "hello",
+ "Waited too long for mixed script to run in Test 3");
+ let p2 = ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p2").innerHTML == "bye",
+ "Waited too long for mixed image to load in Test 3");
+ yield Promise.all([ p1, p2 ]);
+ });
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: true});
+});
+
+// Location change - User override on one page doesn't propogate to another page after location change.
+add_task(function* MixedTest4() {
+ var url = gHttpTestRoot2 + "file_bug822367_4.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest4A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest4B() {
+ let url = gHttpTestRoot + "file_bug822367_4B.html";
+ yield ContentTask.spawn(gTestBrowser, url, function* (wantedUrl) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.location == wantedUrl,
+ "Waited too long for mixed script to run in Test 4");
+ });
+});
+
+add_task(function* MixedTest4C() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "",
+ "Mixed script loaded in test 4 after location change!");
+ });
+});
+
+// Mixed script attempts to load in a document.open()
+add_task(function* MixedTest5() {
+ var url = gHttpTestRoot + "file_bug822367_5.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest5A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest5B() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "hello",
+ "Waited too long for mixed script to run in Test 5");
+ });
+});
+
+// Mixed script attempts to load in a document.open() that is within an iframe.
+add_task(function* MixedTest6() {
+ var url = gHttpTestRoot2 + "file_bug822367_6.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest6A() {
+ gTestBrowser.removeEventListener("load", MixedTest6A, true);
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+
+ yield BrowserTestUtils.waitForCondition(
+ () => gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
+ "Waited too long for control center to get mixed active blocked state");
+});
+
+add_task(function* MixedTest6B() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest6C() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ function test() {
+ try {
+ return content.document.getElementById("f1").contentDocument.getElementById("p1").innerHTML == "hello";
+ } catch (e) {
+ return false;
+ }
+ }
+
+ yield ContentTaskUtils.waitForCondition(test, "Waited too long for mixed script to run in Test 6");
+ });
+});
+
+add_task(function* MixedTest6D() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+});
+
+add_task(function* cleanup() {
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug832435.js b/browser/base/content/test/general/browser_bug832435.js
new file mode 100644
index 000000000..6be2604cd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug832435.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+ ok(true, "Starting up");
+
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener("focus", function onFocus() {
+ gURLBar.removeEventListener("focus", onFocus);
+ ok(true, "Invoked onfocus handler");
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+
+ // javscript: URIs are evaluated async.
+ SimpleTest.executeSoon(function() {
+ ok(true, "Evaluated without crashing");
+ finish();
+ });
+ });
+ gURLBar.inputField.value = "javascript: var foo = '11111111'; ";
+ gURLBar.focus();
+}
diff --git a/browser/base/content/test/general/browser_bug839103.js b/browser/base/content/test/general/browser_bug839103.js
new file mode 100644
index 000000000..5240c92ed
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug839103.js
@@ -0,0 +1,120 @@
+const gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (browser) {
+ yield ContentTask.spawn(browser, gTestRoot, testBody);
+ });
+});
+
+// This function runs entirely in the content process. It doesn't have access
+// any free variables in this file.
+function* testBody(testRoot) {
+ const gStyleSheet = "bug839103.css";
+
+ let loaded = ContentTaskUtils.waitForEvent(this, "load", true);
+ content.location = testRoot + "test_bug839103.html";
+
+ yield loaded;
+ function unexpectedContentEvent(event) {
+ ok(false, "Received a " + event.type + " event on content");
+ }
+
+ // We've seen the original stylesheet in the document.
+ // Now add a stylesheet on the fly and make sure we see it.
+ let doc = content.document;
+ doc.styleSheetChangeEventsEnabled = true;
+ doc.addEventListener("StyleSheetAdded", unexpectedContentEvent);
+ doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
+ doc.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetAdded", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent);
+
+ let link = doc.createElement("link");
+ link.setAttribute("rel", "stylesheet");
+ link.setAttribute("type", "text/css");
+ link.setAttribute("href", testRoot + gStyleSheet);
+
+ let sheetAdded =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetAdded", true);
+ let stateChanged =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetApplicableStateChanged", true);
+ doc.body.appendChild(link);
+
+ let evt = yield sheetAdded;
+ info("received dynamic style sheet event");
+ is(evt.type, "StyleSheetAdded", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.documentSheet, "style sheet is a document sheet");
+
+ evt = yield stateChanged;
+ info("received dynamic style sheet applicable state change event");
+ is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, true, "evt.applicable has the right value");
+
+ stateChanged =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetApplicableStateChanged", true);
+ link.disabled = true;
+
+ evt = yield stateChanged;
+ is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
+ info("received dynamic style sheet applicable state change event after media=\"\" changed");
+ is(evt.target, doc, "event targets correct document");
+ is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, false, "evt.applicable has the right value");
+
+ let sheetRemoved =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetRemoved", true);
+ doc.body.removeChild(link);
+
+ evt = yield sheetRemoved;
+ info("received dynamic style sheet removal");
+ is(evt.type, "StyleSheetRemoved", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.stylesheet.href.includes(gStyleSheet), "evt.stylesheet is the removed stylesheet");
+
+ let ruleAdded =
+ ContentTaskUtils.waitForEvent(this, "StyleRuleAdded", true);
+ doc.querySelector("style").sheet.insertRule("*{color:black}", 0);
+
+ evt = yield ruleAdded;
+ info("received style rule added event");
+ is(evt.type, "StyleRuleAdded", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+ is(evt.rule.cssText, "* { color: black; }", "evt.rule.cssText has expected value");
+
+ let ruleChanged =
+ ContentTaskUtils.waitForEvent(this, "StyleRuleChanged", true);
+ evt.rule.style.cssText = "color:green";
+
+ evt = yield ruleChanged;
+ ok(true, "received style rule changed event");
+ is(evt.type, "StyleRuleChanged", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+ is(evt.rule.cssText, "* { color: green; }", "evt.rule.cssText has expected value");
+
+ let ruleRemoved =
+ ContentTaskUtils.waitForEvent(this, "StyleRuleRemoved", true);
+ evt.stylesheet.deleteRule(0);
+
+ evt = yield ruleRemoved;
+ info("received style rule removed event");
+ is(evt.type, "StyleRuleRemoved", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+}
diff --git a/browser/base/content/test/general/browser_bug882977.js b/browser/base/content/test/general/browser_bug882977.js
new file mode 100644
index 000000000..ed958e06b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug882977.js
@@ -0,0 +1,29 @@
+"use strict";
+
+/**
+ * Tests that the identity-box shows the chromeUI styling
+ * when viewing about:home in a new window.
+ */
+add_task(function*() {
+ let homepage = "about:home";
+ yield SpecialPowers.pushPrefEnv({
+ "set": [
+ ["browser.startup.homepage", homepage],
+ ["browser.startup.page", 1],
+ ]
+ });
+
+ let win = OpenBrowserWindow();
+ yield BrowserTestUtils.firstBrowserLoaded(win, false);
+
+ let browser = win.gBrowser.selectedBrowser;
+ is(browser.currentURI.spec, homepage, "Loaded the correct homepage");
+ checkIdentityMode(win);
+
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+function checkIdentityMode(win) {
+ let identityMode = win.document.getElementById("identity-box").className;
+ is(identityMode, "chromeUI", "Identity state should be chromeUI for about:home in a new window");
+}
diff --git a/browser/base/content/test/general/browser_bug902156.js b/browser/base/content/test/general/browser_bug902156.js
new file mode 100644
index 000000000..74969ead4
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug902156.js
@@ -0,0 +1,174 @@
+/*
+ * Description of the Tests for
+ * - Bug 902156: Persist "disable protection" option for Mixed Content Blocker
+ *
+ * 1. Navigate to the same domain via document.location
+ * - Load a html page which has mixed content
+ * - Control Center button to disable protection appears - we disable it
+ * - Load a new page from the same origin using document.location
+ * - Control Center button should not appear anymore!
+ *
+ * 2. Navigate to the same domain via simulateclick for a link on the page
+ * - Load a html page which has mixed content
+ * - Control Center button to disable protection appears - we disable it
+ * - Load a new page from the same origin simulating a click
+ * - Control Center button should not appear anymore!
+ *
+ * 3. Navigate to a differnet domain and show the content is still blocked
+ * - Load a different html page which has mixed content
+ * - Control Center button to disable protection should appear again because
+ * we navigated away from html page where we disabled the protection.
+ *
+ * Note, for all tests we set gHttpTestRoot to use 'https'.
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+// We alternate for even and odd test cases to simulate different hosts
+const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/";
+
+var origBlockActive;
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+});
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+// ------------------------ Test 1 ------------------------------
+
+function test1A() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test1B);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ // Disable Mixed Content Protection for the page (and reload)
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+}
+
+function test1B() {
+ var expected = "Mixed Content Blocker disabled";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test1C, "Error: Waited too long for mixed script to run in Test 1B");
+}
+
+function test1C() {
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1C");
+
+ // The Script loaded after we disabled the page, now we are going to reload the
+ // page and see if our decision is persistent
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test1D);
+
+ var url = gHttpTestRoot1 + "file_bug902156_2.html";
+ gTestBrowser.loadURI(url);
+}
+
+function test1D() {
+ // The Control Center button should appear but isMixedContentBlocked should be NOT true,
+ // because our decision of disabling the mixed content blocker is persistent.
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1D");
+
+ // move on to Test 2
+ test2();
+}
+
+// ------------------------ Test 2 ------------------------------
+
+function test2() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test2A);
+ var url = gHttpTestRoot2 + "file_bug902156_2.html";
+ gTestBrowser.loadURI(url);
+}
+
+function test2A() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test2B);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ // Disable Mixed Content Protection for the page (and reload)
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+}
+
+function test2B() {
+ var expected = "Mixed Content Blocker disabled";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test2C, "Error: Waited too long for mixed script to run in Test 2B");
+}
+
+function test2C() {
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2C");
+
+ // The Script loaded after we disabled the page, now we are going to reload the
+ // page and see if our decision is persistent
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test2D);
+
+ // reload the page using the provided link in the html file
+ var mctestlink = content.document.getElementById("mctestlink");
+ mctestlink.click();
+}
+
+function test2D() {
+ // The Control Center button should appear but isMixedContentBlocked should be NOT true,
+ // because our decision of disabling the mixed content blocker is persistent.
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2D");
+
+ // move on to Test 3
+ test3();
+}
+
+// ------------------------ Test 3 ------------------------------
+
+function test3() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test3A);
+ var url = gHttpTestRoot1 + "file_bug902156_3.html";
+ gTestBrowser.loadURI(url);
+}
+
+function test3A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ // We are done with tests, clean up
+ cleanUpAfterTests();
+}
+
+// ------------------------------------------------------
+
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ // Not really sure what this is doing
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ // Starting Test Number 1:
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test1A);
+ var url = gHttpTestRoot1 + "file_bug902156_1.html";
+ gTestBrowser.loadURI(url);
+}
diff --git a/browser/base/content/test/general/browser_bug906190.js b/browser/base/content/test/general/browser_bug906190.js
new file mode 100644
index 000000000..613f50efd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug906190.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the persistence of the "disable protection" option for Mixed Content
+ * Blocker in child tabs (bug 906190).
+ */
+
+requestLongerTimeout(2);
+
+// We use the different urls for testing same origin checks before allowing
+// mixed content on child tabs.
+const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/";
+
+/**
+ * For all tests, we load the pages over HTTPS and test both:
+ * - |CTRL+CLICK|
+ * - |RIGHT CLICK -> OPEN LINK IN TAB|
+ */
+function* doTest(parentTabSpec, childTabSpec, testTaskFn, waitForMetaRefresh) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: parentTabSpec,
+ }, function* (browser) {
+ // As a sanity check, test that active content has been blocked as expected.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ // Disable the Mixed Content Blocker for the page, which reloads it.
+ let promiseReloaded = BrowserTestUtils.browserLoaded(browser);
+ gIdentityHandler.disableMixedContentProtection();
+ yield promiseReloaded;
+
+ // Wait for the script in the page to update the contents of the test div.
+ let testDiv = content.document.getElementById('mctestdiv');
+ yield promiseWaitForCondition(
+ () => testDiv.innerHTML == "Mixed Content Blocker disabled");
+
+ // Add the link for the child tab to the page.
+ let mainDiv = content.document.createElement("div");
+ mainDiv.innerHTML =
+ '<p><a id="linkToOpenInNewTab" href="' + childTabSpec + '">Link</a></p>';
+ content.document.body.appendChild(mainDiv);
+
+ // Execute the test in the child tabs with the two methods to open it.
+ for (let openFn of [simulateCtrlClick, simulateContextMenuOpenInTab]) {
+ let promiseTabLoaded = waitForSomeTabToLoad();
+ openFn(browser);
+ yield promiseTabLoaded;
+ gBrowser.selectTabAtIndex(2);
+
+ if (waitForMetaRefresh) {
+ yield waitForSomeTabToLoad();
+ }
+
+ yield testTaskFn();
+
+ gBrowser.removeCurrentTab();
+ }
+ });
+}
+
+function simulateCtrlClick(browser) {
+ BrowserTestUtils.synthesizeMouseAtCenter("#linkToOpenInNewTab",
+ { ctrlKey: true, metaKey: true },
+ browser);
+}
+
+function simulateContextMenuOpenInTab(browser) {
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ // These are operations that must be executed synchronously with the event.
+ document.getElementById("context-openlinkintab").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter("#linkToOpenInNewTab",
+ { type: "contextmenu", button: 2 },
+ browser);
+}
+
+// Waits for a load event somewhere in the browser but ignore events coming
+// from <xul:browser>s without a tab assigned. That are most likely browsers
+// that preload the new tab page.
+function waitForSomeTabToLoad() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("load", function onLoad(event) {
+ let tab = gBrowser._getTabForContentWindow(event.target.defaultView.top);
+ if (tab) {
+ gBrowser.removeEventListener("load", onLoad, true);
+ resolve();
+ }
+ }, true);
+ });
+}
+
+/**
+ * Ensure the Mixed Content Blocker is enabled.
+ */
+add_task(function* test_initialize() {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+ "set": [["security.mixed_content.block_active_content", true]],
+ }, resolve));
+});
+
+/**
+ * 1. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a subpage from the same origin in a new tab simulating a click
+ * - Doorhanger should >> NOT << appear anymore!
+ */
+add_task(function* test_same_origin() {
+ yield doTest(gHttpTestRoot1 + "file_bug906190_1.html",
+ gHttpTestRoot1 + "file_bug906190_2.html", function* () {
+ // The doorhanger should appear but activeBlocked should be >> NOT << true,
+ // because our decision of disabling the mixed content blocker is persistent
+ // across tabs.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: true, activeBlocked: false, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker disabled", "OK: Executed mixed script");
+ });
+});
+
+/**
+ * 2. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from a different origin in a new tab simulating a click
+ * - Doorhanger >> SHOULD << appear again!
+ */
+add_task(function* test_different_origin() {
+ yield doTest(gHttpTestRoot1 + "file_bug906190_2.html",
+ gHttpTestRoot2 + "file_bug906190_2.html", function* () {
+ // The doorhanger should appear and activeBlocked should be >> TRUE <<,
+ // because our decision of disabling the mixed content blocker should only
+ // persist if pages are from the same domain.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker enabled", "OK: Blocked mixed script");
+ });
+});
+
+/**
+ * 3. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from the same origin using meta-refresh
+ * - Doorhanger should >> NOT << appear again!
+ */
+add_task(function* test_same_origin_metarefresh_same_origin() {
+ // file_bug906190_3_4.html redirects to page test1.example.com/* using meta-refresh
+ yield doTest(gHttpTestRoot1 + "file_bug906190_1.html",
+ gHttpTestRoot1 + "file_bug906190_3_4.html", function* () {
+ // The doorhanger should appear but activeBlocked should be >> NOT << true!
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: true, activeBlocked: false, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker disabled", "OK: Executed mixed script");
+ }, true);
+});
+
+/**
+ * 4. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from a different origin using meta-refresh
+ * - Doorhanger >> SHOULD << appear again!
+ */
+add_task(function* test_same_origin_metarefresh_different_origin() {
+ yield doTest(gHttpTestRoot2 + "file_bug906190_1.html",
+ gHttpTestRoot2 + "file_bug906190_3_4.html", function* () {
+ // The doorhanger should appear and activeBlocked should be >> TRUE <<.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker enabled", "OK: Blocked mixed script");
+ }, true);
+});
+
+/**
+ * 5. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from the same origin using 302 redirect
+ */
+add_task(function* test_same_origin_302redirect_same_origin() {
+ // the sjs files returns a 302 redirect- note, same origins
+ yield doTest(gHttpTestRoot1 + "file_bug906190_1.html",
+ gHttpTestRoot1 + "file_bug906190.sjs", function* () {
+ // The doorhanger should appear but activeBlocked should be >> NOT << true.
+ // Currently it is >> TRUE << - see follow up bug 914860
+ ok(!gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
+ "OK: Mixed Content is NOT being blocked");
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker disabled", "OK: Executed mixed script");
+ });
+});
+
+/**
+ * 6. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from a different origin using 302 redirect
+ */
+add_task(function* test_same_origin_302redirect_different_origin() {
+ // the sjs files returns a 302 redirect - note, different origins
+ yield doTest(gHttpTestRoot2 + "file_bug906190_1.html",
+ gHttpTestRoot2 + "file_bug906190.sjs", function* () {
+ // The doorhanger should appear and activeBlocked should be >> TRUE <<.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker enabled", "OK: Blocked mixed script");
+ });
+});
+
+/**
+ * 7. - Test memory leak issue on redirection error. See Bug 1269426.
+ */
+add_task(function* test_bad_redirection() {
+ // the sjs files returns a 302 redirect - note, different origins
+ yield doTest(gHttpTestRoot2 + "file_bug906190_1.html",
+ gHttpTestRoot2 + "file_bug906190.sjs?bad-redirection=1", function* () {
+ // Nothing to do. Just see if memory leak is reported in the end.
+ ok(true, "Nothing to do");
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug963945.js b/browser/base/content/test/general/browser_bug963945.js
new file mode 100644
index 000000000..4531964b0
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug963945.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This test ensures the about:addons tab is only
+ * opened one time when in private browsing.
+ */
+
+add_task(function* test() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ let tab = win.gBrowser.selectedTab = win.gBrowser.addTab("about:addons");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield promiseWaitForFocus(win);
+
+ EventUtils.synthesizeKey("a", { ctrlKey: true, shiftKey: true }, win);
+
+ is(win.gBrowser.tabs.length, 2, "about:addons tab was re-focused.");
+ is(win.gBrowser.currentURI.spec, "about:addons", "Addons tab was opened.");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/general/browser_bug970746.js b/browser/base/content/test/general/browser_bug970746.js
new file mode 100644
index 000000000..623623e55
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug970746.js
@@ -0,0 +1,121 @@
+/* Make sure context menu includes option to search hyperlink text on search engine */
+
+add_task(function *() {
+ const url = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug970746.xhtml";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ const ellipsis = "\u2026";
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ // Tests if the "Search <engine> for '<some terms>'" context menu item is shown for the
+ // given query string of an element. Tests to make sure label includes the proper search terms.
+ //
+ // Each test:
+ //
+ // id: The id of the element to test.
+ // isSelected: Flag to enable selecting (text highlight) the contents of the element
+ // shouldBeShown: The display state of the menu item
+ // expectedLabelContents: The menu item label should contain a portion of this string.
+ // Will only be tested if shouldBeShown is true.
+ let tests = [
+ {
+ id: "link",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a link!",
+ },
+ {
+ id: "link",
+ isSelected: false,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a link!",
+ },
+ {
+ id: "longLink",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a really lo" + ellipsis,
+ },
+ {
+ id: "longLink",
+ isSelected: false,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a really lo" + ellipsis,
+ },
+ {
+ id: "plainText",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "Right clicking " + ellipsis,
+ },
+ {
+ id: "plainText",
+ isSelected: false,
+ shouldBeShown: false,
+ },
+ {
+ id: "mixedContent",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm some text, " + ellipsis,
+ },
+ {
+ id: "mixedContent",
+ isSelected: false,
+ shouldBeShown: false,
+ },
+ {
+ id: "partialLink",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "link selection",
+ },
+ {
+ id: "partialLink",
+ isSelected: false,
+ shouldBeShown: true,
+ expectedLabelContents: "A partial link " + ellipsis,
+ },
+ {
+ id: "surrogatePair",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "This character\uD83D\uDD25" + ellipsis,
+ }
+ ];
+
+ for (let test of tests) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser,
+ { selectElement: test.isSelected ? test.id : null },
+ function* (arg) {
+ let selection = content.getSelection();
+ selection.removeAllRanges();
+
+ if (arg.selectElement) {
+ selection.selectAllChildren(content.document.getElementById(arg.selectElement));
+ }
+ });
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#" + test.id,
+ { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ let menuItem = document.getElementById("context-searchselect");
+ is(menuItem.hidden, !test.shouldBeShown,
+ "search context menu item is shown for '#" + test.id + "' and selected is '" + test.isSelected + "'");
+
+ if (test.shouldBeShown) {
+ ok(menuItem.label.includes(test.expectedLabelContents),
+ "Menu item text '" + menuItem.label + "' contains the correct search terms '" + test.expectedLabelContents + "'");
+ }
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ // cleanup
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug970746.xhtml b/browser/base/content/test/general/browser_bug970746.xhtml
new file mode 100644
index 000000000..9d78d7147
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug970746.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <a href="http://mozilla.org" id="link">I'm a link!</a>
+ <a href="http://mozilla.org" id="longLink">I'm a really long link and I should be truncated.</a>
+
+ <span id="plainText">
+ Right clicking me when I'm selected should show the menu item.
+ </span>
+ <span id="mixedContent">
+ I'm some text, and <a href="http://mozilla.org">I'm a link!</a>
+ </span>
+
+ <a href="http://mozilla.org">A partial <span id="partialLink">link selection</span></a>
+
+ <span id="surrogatePair">
+ This character🔥 shouldn't be truncated.
+ </span>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/browser_clipboard.js b/browser/base/content/test/general/browser_clipboard.js
new file mode 100644
index 000000000..33c6de52d
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard.js
@@ -0,0 +1,174 @@
+// This test is used to check copy and paste in editable areas to ensure that non-text
+// types (html and images) are copied to and pasted from the clipboard properly.
+
+var testPage = "<body style='margin: 0'>" +
+ " <img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" +
+ " <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" +
+ "</body>";
+
+add_task(function*() {
+ let tab = gBrowser.addTab();
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ gBrowser.selectedTab = tab;
+
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(testPage));
+ yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
+
+ const modifier = (navigator.platform.indexOf("Mac") >= 0) ?
+ Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
+ Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
+
+ // On windows, HTML clipboard includes extra data.
+ // The values are from widget/windows/nsDataObj.cpp.
+ const htmlPrefix = (navigator.platform.indexOf("Win") >= 0) ? "<html><body>\n<!--StartFragment-->" : "";
+ const htmlPostfix = (navigator.platform.indexOf("Win") >= 0) ? "<!--EndFragment-->\n</body>\n</html>" : "";
+
+ yield ContentTask.spawn(browser, { modifier, htmlPrefix, htmlPostfix }, function* (arg) {
+ var doc = content.document;
+ var main = doc.getElementById("main");
+ main.focus();
+
+ const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ function sendKey(key) {
+ if (utils.sendKeyEvent("keydown", key, 0, arg.modifier)) {
+ utils.sendKeyEvent("keypress", key, key.charCodeAt(0), arg.modifier);
+ }
+ utils.sendKeyEvent("keyup", key, 0, arg.modifier);
+ }
+
+ // Select an area of the text.
+ let selection = doc.getSelection();
+ selection.modify("move", "left", "line");
+ selection.modify("move", "right", "character");
+ selection.modify("move", "right", "character");
+ selection.modify("move", "right", "character");
+ selection.modify("extend", "right", "word");
+ selection.modify("extend", "right", "word");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("copy", function copyEvent(event) {
+ removeEventListener("copy", copyEvent, true);
+ // The data is empty as the selection is copied during the event default phase.
+ Assert.equal(event.clipboardData.mozItemCount, 0, "Zero items on clipboard");
+ resolve();
+ }, true)
+
+ sendKey("c");
+ });
+
+ selection.modify("move", "right", "line");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("paste", function copyEvent(event) {
+ removeEventListener("paste", copyEvent, true);
+ let clipboardData = event.clipboardData;
+ Assert.equal(clipboardData.mozItemCount, 1, "One item on clipboard");
+ Assert.equal(clipboardData.types.length, 2, "Two types on clipboard");
+ Assert.equal(clipboardData.types[0], "text/html", "text/html on clipboard");
+ Assert.equal(clipboardData.types[1], "text/plain", "text/plain on clipboard");
+ Assert.equal(clipboardData.getData("text/html"), arg.htmlPrefix +
+ "t <b>Bold</b>" + arg.htmlPostfix, "text/html value");
+ Assert.equal(clipboardData.getData("text/plain"), "t Bold", "text/plain value");
+ resolve();
+ }, true)
+ sendKey("v");
+ });
+
+ Assert.equal(main.innerHTML, "Test <b>Bold</b> After Textt <b>Bold</b>", "Copy and paste html");
+
+ selection.modify("extend", "left", "word");
+ selection.modify("extend", "left", "word");
+ selection.modify("extend", "left", "character");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("cut", function copyEvent(event) {
+ removeEventListener("cut", copyEvent, true);
+ event.clipboardData.setData("text/plain", "Some text");
+ event.clipboardData.setData("text/html", "<i>Italic</i> ");
+ selection.deleteFromDocument();
+ event.preventDefault();
+ resolve();
+ }, true)
+ sendKey("x");
+ });
+
+ selection.modify("move", "left", "line");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("paste", function copyEvent(event) {
+ removeEventListener("paste", copyEvent, true);
+ let clipboardData = event.clipboardData;
+ Assert.equal(clipboardData.mozItemCount, 1, "One item on clipboard 2");
+ Assert.equal(clipboardData.types.length, 2, "Two types on clipboard 2");
+ Assert.equal(clipboardData.types[0], "text/html", "text/html on clipboard 2");
+ Assert.equal(clipboardData.types[1], "text/plain", "text/plain on clipboard 2");
+ Assert.equal(clipboardData.getData("text/html"), arg.htmlPrefix +
+ "<i>Italic</i> " + arg.htmlPostfix, "text/html value 2");
+ Assert.equal(clipboardData.getData("text/plain"), "Some text", "text/plain value 2");
+ resolve();
+ }, true)
+ sendKey("v");
+ });
+
+ Assert.equal(main.innerHTML, "<i>Italic</i> Test <b>Bold</b> After<b></b>",
+ "Copy and paste html 2");
+ });
+
+ // Next, check that the Copy Image command works.
+
+ // The context menu needs to be opened to properly initialize for the copy
+ // image command to run.
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenuShown = promisePopupShown(contextMenu);
+ BrowserTestUtils.synthesizeMouseAtCenter("#img", { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+ yield contextMenuShown;
+
+ document.getElementById("context-copyimage-contents").doCommand();
+
+ contextMenu.hidePopup();
+ yield promisePopupHidden(contextMenu);
+
+ // Focus the content again
+ yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
+
+ yield ContentTask.spawn(browser, { modifier, htmlPrefix, htmlPostfix }, function* (arg) {
+ var doc = content.document;
+ var main = doc.getElementById("main");
+ main.focus();
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("paste", function copyEvent(event) {
+ removeEventListener("paste", copyEvent, true);
+ let clipboardData = event.clipboardData;
+
+ // DataTransfer doesn't support the image types yet, so only text/html
+ // will be present.
+ if (clipboardData.getData("text/html") !== arg.htmlPrefix +
+ '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ arg.htmlPostfix) {
+ reject('Clipboard Data did not contain an image, was ' + clipboardData.getData("text/html"));
+ }
+ resolve();
+ }, true)
+
+ const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ if (utils.sendKeyEvent("keydown", "v", 0, arg.modifier)) {
+ utils.sendKeyEvent("keypress", "v", "v".charCodeAt(0), arg.modifier);
+ }
+ utils.sendKeyEvent("keyup", "v", 0, arg.modifier);
+ });
+
+ // The new content should now include an image.
+ Assert.equal(main.innerHTML, '<i>Italic</i> <img id="img" tabindex="1" ' +
+ 'src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ 'Test <b>Bold</b> After<b></b>', "Paste after copy image");
+ });
+
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_clipboard_pastefile.js b/browser/base/content/test/general/browser_clipboard_pastefile.js
new file mode 100644
index 000000000..fe87284f3
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard_pastefile.js
@@ -0,0 +1,62 @@
+// This test is used to check that pasting files removes all non-file data from
+// event.clipboardData.
+
+add_task(function*() {
+ var textbox = document.createElement("textbox");
+ document.documentElement.appendChild(textbox);
+
+ textbox.focus();
+ textbox.value = "Text";
+ textbox.select();
+
+ yield new Promise((resolve, reject) => {
+ textbox.addEventListener("copy", function copyEvent(event) {
+ textbox.removeEventListener("copy", copyEvent, true);
+ event.clipboardData.setData("text/plain", "Alternate");
+ // For this test, it doesn't matter that the file isn't actually a file.
+ event.clipboardData.setData("application/x-moz-file", "Sample");
+ event.preventDefault();
+ resolve();
+ }, true)
+
+ EventUtils.synthesizeKey("c", { accelKey: true });
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "https://example.com/browser/browser/base/content/test/general/clipboard_pastefile.html");
+ let browser = tab.linkedBrowser;
+
+ yield ContentTask.spawn(browser, { }, function* (arg) {
+ content.document.getElementById("input").focus();
+ });
+
+ yield BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser);
+
+ let output = yield ContentTask.spawn(browser, { }, function* (arg) {
+ return content.document.getElementById("output").textContent;
+ });
+ is (output, "Passed", "Paste file");
+
+ textbox.focus();
+
+ yield new Promise((resolve, reject) => {
+ textbox.addEventListener("paste", function copyEvent(event) {
+ textbox.removeEventListener("paste", copyEvent, true);
+
+ let dt = event.clipboardData;
+ is(dt.types.length, 3, "number of types");
+ ok(dt.types.includes("text/plain"), "text/plain exists in types");
+ ok(dt.mozTypesAt(0).contains("text/plain"), "text/plain exists in mozTypesAt");
+ is(dt.getData("text/plain"), "Alternate", "text/plain returned in getData");
+ is(dt.mozGetDataAt("text/plain", 0), "Alternate", "text/plain returned in mozGetDataAt");
+
+ resolve();
+ }, true);
+
+ EventUtils.synthesizeKey("v", { accelKey: true });
+ });
+
+ document.documentElement.removeChild(textbox);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_contentAltClick.js b/browser/base/content/test/general/browser_contentAltClick.js
new file mode 100644
index 000000000..1a3b0fccc
--- /dev/null
+++ b/browser/base/content/test/general/browser_contentAltClick.js
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for Bug 1109146.
+ * The tests opens a new tab and alt + clicks to download files
+ * and confirms those files are on the download list.
+ *
+ * The difference between this and the test "browser_contentAreaClick.js" is that
+ * the code path in e10s uses ContentClick.jsm instead of browser.js::contentAreaClick() util.
+ */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+function setup() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+
+ let testPage =
+ 'data:text/html,' +
+ '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
+ '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
+ '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>';
+
+ return BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+}
+
+function* clean_up() {
+ // Remove downloads.
+ let downloadList = yield Downloads.getList(Downloads.ALL);
+ let downloads = yield downloadList.getAll();
+ for (let download of downloads) {
+ yield downloadList.remove(download);
+ yield download.finalize(true);
+ }
+ // Remove download history.
+ yield PlacesTestUtils.clearHistory();
+
+ gPrefService.clearUserPref("browser.altClickSave");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+add_task(function* test_alt_click()
+{
+ yield setup();
+
+ let downloadList = yield Downloads.getList(Downloads.ALL);
+ let downloads = [];
+ let downloadView;
+ // When 1 download has been attempted then resolve the promise.
+ let finishedAllDownloads = new Promise( (resolve) => {
+ downloadView = {
+ onDownloadAdded: function (aDownload) {
+ downloads.push(aDownload);
+ resolve();
+ },
+ };
+ });
+ yield downloadList.addView(downloadView);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#commonlink", {altKey: true}, gBrowser.selectedBrowser);
+
+ // Wait for all downloads to be added to the download list.
+ yield finishedAllDownloads;
+ yield downloadList.removeView(downloadView);
+
+ is(downloads.length, 1, "1 downloads");
+ is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #commonlink element");
+
+ yield* clean_up();
+});
+
+add_task(function* test_alt_click_on_xlinks()
+{
+ yield setup();
+
+ let downloadList = yield Downloads.getList(Downloads.ALL);
+ let downloads = [];
+ let downloadView;
+ // When all 2 downloads have been attempted then resolve the promise.
+ let finishedAllDownloads = new Promise( (resolve) => {
+ downloadView = {
+ onDownloadAdded: function (aDownload) {
+ downloads.push(aDownload);
+ if (downloads.length == 2) {
+ resolve();
+ }
+ },
+ };
+ });
+ yield downloadList.addView(downloadView);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#mathxlink", {altKey: true}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#svgxlink", {altKey: true}, gBrowser.selectedBrowser);
+
+ // Wait for all downloads to be added to the download list.
+ yield finishedAllDownloads;
+ yield downloadList.removeView(downloadView);
+
+ is(downloads.length, 2, "2 downloads");
+ is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #mathxlink element");
+ is(downloads[1].source.url, "http://mochi.test/moz/", "Downloaded #svgxlink element");
+
+ yield* clean_up();
+});
diff --git a/browser/base/content/test/general/browser_contentAreaClick.js b/browser/base/content/test/general/browser_contentAreaClick.js
new file mode 100644
index 000000000..facdfb498
--- /dev/null
+++ b/browser/base/content/test/general/browser_contentAreaClick.js
@@ -0,0 +1,307 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 549340.
+ * Test for browser.js::contentAreaClick() util.
+ *
+ * The test opens a new browser window, then replaces browser.js methods invoked
+ * by contentAreaClick with a mock function that tracks which methods have been
+ * called.
+ * Each sub-test synthesizes a mouse click event on links injected in content,
+ * the event is collected by a click handler that ensures that contentAreaClick
+ * correctly prevent default events, and follows the correct code path.
+ */
+
+var gTests = [
+
+ {
+ desc: "Simple left click",
+ setup: function() {},
+ clean: function() {},
+ event: {},
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [],
+ preventDefault: false,
+ },
+
+ {
+ desc: "Ctrl/Cmd left click",
+ setup: function() {},
+ clean: function() {},
+ event: { ctrlKey: true,
+ metaKey: true },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ // The next test was once handling feedService.forcePreview(). Now it should
+ // just be like Alt click.
+ {
+ desc: "Shift+Alt left click",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { shiftKey: true,
+ altKey: true },
+ targets: [ "commonlink", "maplink" ],
+ expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Shift+Alt left click on XLinks",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { shiftKey: true,
+ altKey: true },
+ targets: [ "mathxlink", "svgxlink"],
+ expectedInvokedMethods: [ "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Shift click",
+ setup: function() {},
+ clean: function() {},
+ event: { shiftKey: true },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Alt click",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { altKey: true },
+ targets: [ "commonlink", "maplink" ],
+ expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Alt click on XLinks",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { altKey: true },
+ targets: [ "mathxlink", "svgxlink" ],
+ expectedInvokedMethods: [ "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Panel click",
+ setup: function() {},
+ clean: function() {},
+ event: {},
+ targets: [ "panellink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "loadURI" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Simple middle click opentab",
+ setup: function() {},
+ clean: function() {},
+ event: { button: 1 },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Simple middle click openwin",
+ setup: function() {
+ gPrefService.setBoolPref("browser.tabs.opentabfor.middleclick", false);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
+ },
+ event: { button: 1 },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Middle mouse paste",
+ setup: function() {
+ gPrefService.setBoolPref("middlemouse.contentLoadURL", true);
+ gPrefService.setBoolPref("general.autoScroll", false);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("middlemouse.contentLoadURL");
+ gPrefService.clearUserPref("general.autoScroll");
+ },
+ event: { button: 1 },
+ targets: [ "emptylink" ],
+ expectedInvokedMethods: [ "middleMousePaste" ],
+ preventDefault: true,
+ },
+
+];
+
+// Array of method names that will be replaced in the new window.
+var gReplacedMethods = [
+ "middleMousePaste",
+ "urlSecurityCheck",
+ "loadURI",
+ "gatherTextUnder",
+ "saveURL",
+ "openLinkIn",
+ "getShortcutOrURIAndPostData",
+];
+
+// Reference to the new window.
+var gTestWin = null;
+
+// List of methods invoked by a specific call to contentAreaClick.
+var gInvokedMethods = [];
+
+// The test currently running.
+var gCurrentTest = null;
+
+function test() {
+ waitForExplicitFinish();
+
+ gTestWin = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+ whenDelayedStartupFinished(gTestWin, function () {
+ info("Browser window opened");
+ waitForFocus(function() {
+ info("Browser window focused");
+ waitForFocus(function() {
+ info("Setting up browser...");
+ setupTestBrowserWindow();
+ info("Running tests...");
+ executeSoon(runNextTest);
+ }, gTestWin.content, true);
+ }, gTestWin);
+ });
+}
+
+// Click handler used to steal click events.
+var gClickHandler = {
+ handleEvent: function (event) {
+ let linkId = event.target.id || event.target.localName;
+ is(event.type, "click",
+ gCurrentTest.desc + ":Handler received a click event on " + linkId);
+
+ let isPanelClick = linkId == "panellink";
+ gTestWin.contentAreaClick(event, isPanelClick);
+ let prevent = event.defaultPrevented;
+ is(prevent, gCurrentTest.preventDefault,
+ gCurrentTest.desc + ": event.defaultPrevented is correct (" + prevent + ")")
+
+ // Check that all required methods have been called.
+ gCurrentTest.expectedInvokedMethods.forEach(function(aExpectedMethodName) {
+ isnot(gInvokedMethods.indexOf(aExpectedMethodName), -1,
+ gCurrentTest.desc + ":" + aExpectedMethodName + " was invoked");
+ });
+
+ if (gInvokedMethods.length != gCurrentTest.expectedInvokedMethods.length) {
+ ok(false, "Wrong number of invoked methods");
+ gInvokedMethods.forEach(method => info(method + " was invoked"));
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ executeSoon(runNextTest);
+ }
+}
+
+// Wraps around the methods' replacement mock function.
+function wrapperMethod(aInvokedMethods, aMethodName) {
+ return function () {
+ aInvokedMethods.push(aMethodName);
+ // At least getShortcutOrURIAndPostData requires to return url
+ return (aMethodName == "getShortcutOrURIAndPostData") ? arguments.url : arguments[0];
+ }
+}
+
+function setupTestBrowserWindow() {
+ // Steal click events and don't propagate them.
+ gTestWin.addEventListener("click", gClickHandler, true);
+
+ // Replace methods.
+ gReplacedMethods.forEach(function (aMethodName) {
+ gTestWin["old_" + aMethodName] = gTestWin[aMethodName];
+ gTestWin[aMethodName] = wrapperMethod(gInvokedMethods, aMethodName);
+ });
+
+ // Inject links in content.
+ let doc = gTestWin.content.document;
+ let mainDiv = doc.createElement("div");
+ mainDiv.innerHTML =
+ '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
+ '<p><a id="panellink" href="http://mochi.test/moz/">Panel link</a></p>' +
+ '<p><a id="emptylink">Empty link</a></p>' +
+ '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
+ '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>' +
+ '<p><map name="map" id="map"><area href="http://mochi.test/moz/" shape="rect" coords="0,0,128,128" /></map><img id="maplink" usemap="#map" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAABGdBTUEAALGPC%2FxhBQAAAOtJREFUeF7t0IEAAAAAgKD9qRcphAoDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGBgwIAAAT0N51AAAAAASUVORK5CYII%3D"/></p>'
+ doc.body.appendChild(mainDiv);
+}
+
+function runNextTest() {
+ if (!gCurrentTest) {
+ gCurrentTest = gTests.shift();
+ gCurrentTest.setup();
+ }
+
+ if (gCurrentTest.targets.length == 0) {
+ info(gCurrentTest.desc + ": cleaning up...")
+ gCurrentTest.clean();
+
+ if (gTests.length > 0) {
+ gCurrentTest = gTests.shift();
+ gCurrentTest.setup();
+ }
+ else {
+ finishTest();
+ return;
+ }
+ }
+
+ // Move to next target.
+ gInvokedMethods.length = 0;
+ let target = gCurrentTest.targets.shift();
+
+ info(gCurrentTest.desc + ": testing " + target);
+
+ // Fire click event.
+ let targetElt = gTestWin.content.document.getElementById(target);
+ ok(targetElt, gCurrentTest.desc + ": target is valid (" + targetElt.id + ")");
+ EventUtils.synthesizeMouseAtCenter(targetElt, gCurrentTest.event, gTestWin.content);
+}
+
+function finishTest() {
+ info("Restoring browser...");
+ gTestWin.removeEventListener("click", gClickHandler, true);
+
+ // Restore original methods.
+ gReplacedMethods.forEach(function (aMethodName) {
+ gTestWin[aMethodName] = gTestWin["old_" + aMethodName];
+ delete gTestWin["old_" + aMethodName];
+ });
+
+ gTestWin.close();
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_contentSearchUI.js b/browser/base/content/test/general/browser_contentSearchUI.js
new file mode 100644
index 000000000..003f80aff
--- /dev/null
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -0,0 +1,771 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_PAGE_BASENAME = "contentSearchUI.html";
+const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js";
+const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
+
+const TEST_MSG = "ContentSearchUIControllerTest";
+
+requestLongerTimeout(2);
+
+add_task(function* emptyInput() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_BACK_SPACE");
+ checkState(state, "", [], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* blur() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("blur");
+ checkState(state, "x", [], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* upDownKeys() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ // Cycle down the suggestions starting from no selection.
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ // Cycle up starting from no selection.
+ state = yield msg("key", "VK_UP");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* rightLeftKeys() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_LEFT");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_LEFT");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_RIGHT");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_RIGHT");
+ checkState(state, "x", [], -1);
+
+ state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ // This should make the xfoo suggestion sticky. To make sure it sticks,
+ // trigger suggestions again and cycle through them by pressing Down until
+ // nothing is selected again.
+ state = yield msg("key", "VK_RIGHT");
+ checkState(state, "xfoo", [], -1);
+
+ state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* tabKey() {
+ yield setUp();
+ yield msg("key", { key: "x", waitForSuggestions: true });
+
+ let state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+
+ state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
+ checkState(state, "x", [], -1);
+
+ yield setUp();
+
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+
+ for (let i = 0; i < 3; ++i) {
+ state = yield msg("key", "VK_TAB");
+ }
+ checkState(state, "x", [], -1);
+
+ yield setUp();
+
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, 0);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", [], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* cycleSuggestions() {
+ yield setUp();
+ yield msg("key", { key: "x", waitForSuggestions: true });
+
+ let cycle = Task.async(function* (aSelectedButtonIndex) {
+ let modifiers = {
+ shiftKey: true,
+ accelKey: true,
+ };
+
+ let state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
+ });
+
+ yield cycle();
+
+ // Repeat with a one-off selected.
+ let state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+ yield cycle(0);
+
+ // Repeat with the settings button selected.
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+ yield cycle(1);
+
+ yield msg("reset");
+});
+
+add_task(function* cycleOneOffs() {
+ yield setUp();
+ yield msg("key", { key: "x", waitForSuggestions: true });
+
+ yield msg("addDuplicateOneOff");
+
+ let state = yield msg("key", "VK_DOWN");
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ let modifiers = {
+ altKey: true,
+ };
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ // If the settings button is selected, pressing alt+up/down should select the
+ // last/first one-off respectively (and deselect the settings button).
+ yield msg("key", "VK_TAB");
+ yield msg("key", "VK_TAB");
+ state = yield msg("key", "VK_TAB"); // Settings button selected.
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ yield msg("removeLastOneOff");
+ yield msg("reset");
+});
+
+add_task(function* mouse() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("mousemove", 0);
+ checkState(state, "x", ["xfoo", "xbar"], 0);
+
+ state = yield msg("mousemove", 1);
+ checkState(state, "x", ["xfoo", "xbar"], 1);
+
+ state = yield msg("mousemove", 2);
+ checkState(state, "x", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("mousemove", 3);
+ checkState(state, "x", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("mousemove", -1);
+ checkState(state, "x", ["xfoo", "xbar"], 1);
+
+ yield msg("reset");
+ yield setUp();
+
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("mousemove", 0);
+ checkState(state, "x", ["xfoo", "xbar"], 0);
+
+ state = yield msg("mousemove", 2);
+ checkState(state, "x", ["xfoo", "xbar"], 0, 0);
+
+ state = yield msg("mousemove", -1);
+ checkState(state, "x", ["xfoo", "xbar"], 0);
+
+ yield msg("reset");
+});
+
+add_task(function* formHistory() {
+ yield setUp();
+
+ // Type an X and add it to form history.
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+ // Wait for Satchel to say it's been added to form history.
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onAdd(subj, topic, data) {
+ if (data == "formhistory-add") {
+ Services.obs.removeObserver(onAdd, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+ yield Promise.all([msg("addInputValueToFormHistory"), deferred.promise]);
+
+ // Reset the input.
+ state = yield msg("reset");
+ checkState(state, "", [], -1);
+
+ // Type an X again. The form history entry should appear.
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
+ -1);
+
+ // Select the form history entry and delete it.
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
+ 0);
+
+ // Wait for Satchel.
+ deferred = Promise.defer();
+ Services.obs.addObserver(function onRemove(subj, topic, data) {
+ if (data == "formhistory-remove") {
+ Services.obs.removeObserver(onRemove, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+
+ state = yield msg("key", "VK_DELETE");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield deferred.promise;
+
+ // Reset the input.
+ state = yield msg("reset");
+ checkState(state, "", [], -1);
+
+ // Type an X again. The form history entry should still be gone.
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* cycleEngines() {
+ yield setUp();
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+
+ let promiseEngineChange = function(newEngineName) {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function resolver(subj, topic, data) {
+ if (data != "engine-current") {
+ return;
+ }
+ SimpleTest.is(subj.name, newEngineName, "Engine cycled correctly");
+ Services.obs.removeObserver(resolver, "browser-search-engine-modified");
+ deferred.resolve();
+ }, "browser-search-engine-modified", false);
+ return deferred.promise;
+ }
+
+ let p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME);
+ yield msg("key", { key: "VK_DOWN", modifiers: { accelKey: true }});
+ yield p;
+
+ p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME);
+ yield msg("key", { key: "VK_UP", modifiers: { accelKey: true }});
+ yield p;
+
+ yield msg("reset");
+});
+
+add_task(function* search() {
+ yield setUp();
+
+ let modifiers = {};
+ ["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true);
+
+ // Test typing a query and pressing enter.
+ let p = msg("waitForSearch");
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ let mesg = yield p;
+ let eventData = {
+ engineName: TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME,
+ searchString: "x",
+ healthReportKey: "test",
+ searchPurpose: "test",
+ originalEvent: modifiers,
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query, then selecting a suggestion and pressing enter.
+ p = msg("waitForSearch");
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo";
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+ eventData.selection = {
+ index: 1,
+ kind: "key",
+ }
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query, then selecting a one-off button and pressing enter.
+ p = msg("waitForSearch");
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", "VK_UP");
+ yield msg("key", "VK_UP");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ delete eventData.selection;
+ eventData.searchString = "x";
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query and clicking the search engine header.
+ p = msg("waitForSearch");
+ modifiers.button = 0;
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("mousemove", -1);
+ yield msg("click", { eltIdx: -1, modifiers: modifiers });
+ mesg = yield p;
+ eventData.originalEvent = modifiers;
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query and then clicking a suggestion.
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("mousemove", 1);
+ yield msg("click", { eltIdx: 1, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo";
+ eventData.selection = {
+ index: 1,
+ kind: "mouse",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query and then clicking a one-off button.
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("mousemove", 3);
+ yield msg("click", { eltIdx: 3, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "x";
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
+ delete eventData.selection;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test selecting a suggestion, then clicking a one-off without deselecting the
+ // suggestion.
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("mousemove", 1);
+ yield msg("mousemove", 3);
+ yield msg("click", { eltIdx: 3, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo"
+ eventData.selection = {
+ index: 1,
+ kind: "mouse",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Same as above, but with the keyboard.
+ delete modifiers.button;
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_TAB");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ eventData.selection = {
+ index: 1,
+ kind: "key",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test searching when using IME composition.
+ let state = yield msg("startComposition", { data: "" });
+ checkState(state, "", [], -1);
+ state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], -1);
+ yield msg("commitComposition");
+ delete modifiers.button;
+ p = msg("waitForSearch");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "x"
+ eventData.originalEvent = modifiers;
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+ delete eventData.selection;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ state = yield msg("startComposition", { data: "" });
+ checkState(state, "", [], -1);
+ state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], -1);
+
+ // Mouse over the first suggestion.
+ state = yield msg("mousemove", 0);
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], 0);
+
+ // Mouse over the second suggestion.
+ state = yield msg("mousemove", 1);
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], 1);
+
+ modifiers.button = 0;
+ p = msg("waitForSearch");
+ yield msg("click", { eltIdx: 1, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo";
+ eventData.originalEvent = modifiers;
+ eventData.selection = {
+ index: 1,
+ kind: "mouse",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Remove form history entries.
+ // Wait for Satchel.
+ let deferred = Promise.defer();
+ let historyCount = 2;
+ Services.obs.addObserver(function onRemove(subj, topic, data) {
+ if (data == "formhistory-remove") {
+ if (--historyCount) {
+ return;
+ }
+ Services.obs.removeObserver(onRemove, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DELETE");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DELETE");
+ yield deferred.promise;
+
+ yield msg("reset");
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield promiseTab();
+ yield setUp();
+ yield msg("reset");
+});
+
+add_task(function* settings() {
+ yield setUp();
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ yield msg("key", "VK_UP");
+ let p = msg("waitForSearchSettings");
+ yield msg("key", "VK_RETURN");
+ yield p;
+
+ yield msg("reset");
+});
+
+var gDidInitialSetUp = false;
+
+function setUp(aNoEngine) {
+ return Task.spawn(function* () {
+ if (!gDidInitialSetUp) {
+ Cu.import("resource:///modules/ContentSearch.jsm");
+ let originalOnMessageSearch = ContentSearch._onMessageSearch;
+ let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines;
+ ContentSearch._onMessageSearch = () => {};
+ ContentSearch._onMessageManageEngines = () => {};
+ registerCleanupFunction(() => {
+ ContentSearch._onMessageSearch = originalOnMessageSearch;
+ ContentSearch._onMessageManageEngines = originalOnMessageManageEngines;
+ });
+ yield setUpEngines();
+ yield promiseTab();
+ gDidInitialSetUp = true;
+ }
+ yield msg("focus");
+ });
+}
+
+function msg(type, data=null) {
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: type,
+ data: data,
+ });
+ let deferred = Promise.defer();
+ gMsgMan.addMessageListener(TEST_MSG, function onMsg(msgObj) {
+ if (msgObj.data.type != type) {
+ return;
+ }
+ gMsgMan.removeMessageListener(TEST_MSG, onMsg);
+ deferred.resolve(msgObj.data.data);
+ });
+ return deferred.promise;
+}
+
+function checkState(actualState, expectedInputVal, expectedSuggestions,
+ expectedSelectedIdx, expectedSelectedButtonIdx) {
+ expectedSuggestions = expectedSuggestions.map(sugg => {
+ return typeof(sugg) == "object" ? sugg : {
+ str: sugg,
+ type: "remote",
+ };
+ });
+
+ if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) {
+ expectedSelectedIdx = expectedSuggestions.length + expectedSelectedButtonIdx;
+ }
+
+ let expectedState = {
+ selectedIndex: expectedSelectedIdx,
+ numSuggestions: expectedSuggestions.length,
+ suggestionAtIndex: expectedSuggestions.map(s => s.str),
+ isFormHistorySuggestionAtIndex:
+ expectedSuggestions.map(s => s.type == "formHistory"),
+
+ tableHidden: expectedSuggestions.length == 0,
+
+ inputValue: expectedInputVal,
+ ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
+ };
+ if (expectedSelectedButtonIdx != undefined) {
+ expectedState.selectedButtonIndex = expectedSelectedButtonIdx;
+ }
+ else if (expectedSelectedIdx < expectedSuggestions.length) {
+ expectedState.selectedButtonIndex = -1;
+ }
+ else {
+ expectedState.selectedButtonIndex = expectedSelectedIdx - expectedSuggestions.length;
+ }
+
+ SimpleTest.isDeeply(actualState, expectedState, "State");
+}
+
+var gMsgMan;
+
+function* promiseTab() {
+ let deferred = Promise.defer();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ registerCleanupFunction(() => BrowserTestUtils.removeTab(tab));
+ let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
+ tab.linkedBrowser.addEventListener("load", function onLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ gMsgMan = tab.linkedBrowser.messageManager;
+ gMsgMan.sendAsyncMessage("ContentSearch", {
+ type: "AddToWhitelist",
+ data: [pageURL],
+ });
+ promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => {
+ let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
+ gMsgMan.loadFrameScript(jsURL, false);
+ deferred.resolve(msg("init"));
+ });
+ }, true, true);
+ openUILinkIn(pageURL, "current");
+ return deferred.promise;
+}
+
+function promiseMsg(name, type, msgMan) {
+ let deferred = Promise.defer();
+ info("Waiting for " + name + " message " + type + "...");
+ msgMan.addMessageListener(name, function onMsg(msgObj) {
+ info("Received " + name + " message " + msgObj.data.type + "\n");
+ if (msgObj.data.type == type) {
+ msgMan.removeMessageListener(name, onMsg);
+ deferred.resolve(msgObj);
+ }
+ });
+ return deferred.promise;
+}
+
+function setUpEngines() {
+ return Task.spawn(function* () {
+ info("Removing default search engines");
+ let currentEngineName = Services.search.currentEngine.name;
+ let currentEngines = Services.search.getVisibleEngines();
+ info("Adding test search engines");
+ let engine1 = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ yield promiseNewSearchEngine(TEST_ENGINE_2_BASENAME);
+ Services.search.currentEngine = engine1;
+ for (let engine of currentEngines) {
+ Services.search.removeEngine(engine);
+ }
+ registerCleanupFunction(() => {
+ Services.search.restoreDefaultEngines();
+ Services.search.currentEngine = Services.search.getEngineByName(currentEngineName);
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_contextmenu.js b/browser/base/content/test/general/browser_contextmenu.js
new file mode 100644
index 000000000..3e0135848
--- /dev/null
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -0,0 +1,996 @@
+"use strict";
+
+let contextMenu;
+let LOGIN_FILL_ITEMS = [
+ "---", null,
+ "fill-login", null,
+ [
+ "fill-login-no-logins", false,
+ "---", null,
+ "fill-login-saved-passwords", true
+ ], null,
+];
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+const example_base = "http://example.com/browser/browser/base/content/test/general/";
+const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+
+Services.scriptloader.loadSubScript(chrome_base + "contextmenu_common.js", this);
+
+// Below are test cases for XUL element
+add_task(function* test_xul_text_link_label() {
+ let url = chrome_base + "subtst_contextmenu_xul.xul";
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield test_contextmenu("#test-xul-text-link-label",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "context-searchselect", true
+ ]
+ );
+
+ // Clean up so won't affect HTML element test cases
+ lastElementSelector = null;
+ gBrowser.removeCurrentTab();
+});
+
+// Below are test cases for HTML element
+
+add_task(function* test_setup_html() {
+ let url = example_base + "subtst_contextmenu.html";
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let videoIframe = doc.querySelector("#test-video-in-iframe");
+ let video = videoIframe.contentDocument.querySelector("video");
+ let awaitPause = ContentTaskUtils.waitForEvent(video, "pause");
+ video.pause();
+ yield awaitPause;
+
+ let audioIframe = doc.querySelector("#test-audio-in-iframe");
+ // media documents always use a <video> tag.
+ let audio = audioIframe.contentDocument.querySelector("video");
+ awaitPause = ContentTaskUtils.waitForEvent(audio, "pause");
+ audio.pause();
+ yield awaitPause;
+ });
+});
+
+let plainTextItems;
+add_task(function* test_plaintext() {
+ plainTextItems = ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ];
+ yield test_contextmenu("#test-text", plainTextItems);
+});
+
+add_task(function* test_link() {
+ yield test_contextmenu("#test-link",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "context-searchselect", true
+ ]
+ );
+});
+
+add_task(function* test_mailto() {
+ yield test_contextmenu("#test-mailto",
+ ["context-copyemail", true,
+ "context-searchselect", true
+ ]
+ );
+});
+
+add_task(function* test_image() {
+ yield test_contextmenu("#test-image",
+ ["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ]
+ );
+});
+
+add_task(function* test_canvas() {
+ yield test_contextmenu("#test-canvas",
+ ["context-viewimage", true,
+ "context-saveimage", true,
+ "context-selectall", true
+ ]
+ );
+});
+
+add_task(function* test_video_ok() {
+ yield test_contextmenu("#test-video-ok",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", true,
+ "context-sendvideo", true,
+ "context-castvideo", null,
+ [], null
+ ]
+ );
+});
+
+add_task(function* test_audio_in_video() {
+ yield test_contextmenu("#test-audio-in-video",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "context-media-showcontrols", true,
+ "---", null,
+ "context-copyaudiourl", true,
+ "---", null,
+ "context-saveaudio", true,
+ "context-sendaudio", true
+ ]
+ );
+});
+
+add_task(function* test_video_bad() {
+ yield test_contextmenu("#test-video-bad",
+ ["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", false,
+ "context-media-playbackrate-100x", false,
+ "context-media-playbackrate-125x", false,
+ "context-media-playbackrate-150x", false,
+ "context-media-playbackrate-200x", false], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", false,
+ "context-sendvideo", true,
+ "context-castvideo", null,
+ [], null
+ ]
+ );
+});
+
+add_task(function* test_video_bad2() {
+ yield test_contextmenu("#test-video-bad2",
+ ["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", false,
+ "context-media-playbackrate-100x", false,
+ "context-media-playbackrate-125x", false,
+ "context-media-playbackrate-150x", false,
+ "context-media-playbackrate-200x", false], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", false,
+ "context-copyvideourl", false,
+ "---", null,
+ "context-savevideo", false,
+ "context-video-saveimage", false,
+ "context-sendvideo", false,
+ "context-castvideo", null,
+ [], null
+ ]
+ );
+});
+
+add_task(function* test_iframe() {
+ yield test_contextmenu("#test-iframe",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ]
+ );
+});
+
+add_task(function* test_video_in_iframe() {
+ yield test_contextmenu("#test-video-in-iframe",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", true,
+ "context-sendvideo", true,
+ "context-castvideo", null,
+ [], null,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null]
+ );
+});
+
+add_task(function* test_audio_in_iframe() {
+ yield test_contextmenu("#test-audio-in-iframe",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "---", null,
+ "context-copyaudiourl", true,
+ "---", null,
+ "context-saveaudio", true,
+ "context-sendaudio", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null]
+ );
+});
+
+add_task(function* test_image_in_iframe() {
+ yield test_contextmenu("#test-image-in-iframe",
+ ["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null]
+ );
+});
+
+add_task(function* test_textarea() {
+ // Disabled since this is seeing spell-check-enabled
+ // instead of spell-add-dictionaries-main
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null,
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-add-dictionaries-main", true,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+ */
+});
+
+add_task(function* test_textarea_spellcheck() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["*chubbiness", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {
+ waitForSpellCheck: true,
+ offsetX: 6,
+ offsetY: 6,
+ postCheckContextMenuFn() {
+ document.getElementById("spell-add-to-dictionary").doCommand();
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_plaintext2() {
+ yield test_contextmenu("#test-text", plainTextItems);
+});
+
+add_task(function* test_undo_add_to_dictionary() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["spell-undo-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {
+ waitForSpellCheck: true,
+ postCheckContextMenuFn() {
+ document.getElementById("spell-undo-add-to-dictionary")
+ .doCommand();
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_contenteditable() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-contenteditable",
+ ["spell-no-suggestions", false,
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {waitForSpellCheck: true}
+ );
+ */
+});
+
+add_task(function* test_copylinkcommand() {
+ yield test_contextmenu("#test-link", null, {
+ postCheckContextMenuFn: function*() {
+ document.commandDispatcher
+ .getControllerForCommand("cmd_copyLink")
+ .doCommand("cmd_copyLink");
+
+ // The easiest way to check the clipboard is to paste the contents
+ // into a textbox.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("test-input");
+ input.focus();
+ input.value = "";
+ });
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("test-input");
+ Assert.equal(input.value, "http://mozilla.com/", "paste for command cmd_paste");
+ });
+ }
+ });
+});
+
+add_task(function* test_pagemenu() {
+ yield test_contextmenu("#test-pagemenu",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "+Plain item", {type: "", icon: "", checked: false, disabled: false},
+ "+Disabled item", {type: "", icon: "", checked: false, disabled: true},
+ "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "---", null,
+ "+Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
+ "+Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "generated-submenu-1", true,
+ ["+Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {postCheckContextMenuFn: function*() {
+ let item = contextMenu.getElementsByAttribute("generateditemid", "1")[0];
+ ok(item, "Got generated XUL menu item");
+ item.doCommand();
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let pagemenu = content.document.getElementById("test-pagemenu");
+ Assert.ok(!pagemenu.hasAttribute("hopeless"), "attribute got removed");
+ });
+ }
+ });
+});
+
+add_task(function* test_dom_full_screen() {
+ yield test_contextmenu("#test-dom-full-screen",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-leave-dom-fullscreen", true,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {
+ shiftkey: true,
+ *preCheckContextMenuFn() {
+ yield pushPrefs(["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"])
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ let full_screen_element = doc.getElementById("test-dom-full-screen");
+ let awaitFullScreenChange =
+ ContentTaskUtils.waitForEvent(win, "fullscreenchange");
+ full_screen_element.requestFullscreen();
+ yield awaitFullScreenChange;
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ let awaitFullScreenChange =
+ ContentTaskUtils.waitForEvent(win, "fullscreenchange");
+ content.document.exitFullscreen();
+ yield awaitFullScreenChange;
+ });
+ }
+ }
+ );
+});
+
+add_task(function* test_pagemenu2() {
+ yield test_contextmenu("#test-text",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {shiftkey: true}
+ );
+});
+
+add_task(function* test_select_text() {
+ yield test_contextmenu("#test-select-text",
+ ["context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ *preCheckContextMenuFn() {
+ yield selectText("#test-select-text");
+ }
+ }
+ );
+});
+
+add_task(function* test_select_text_link() {
+ yield test_contextmenu("#test-select-text-link",
+ ["context-openlinkincurrent", true,
+ "context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ *preCheckContextMenuFn() {
+ yield selectText("#test-select-text-link");
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ win.getSelection().removeAllRanges();
+ });
+ }
+ }
+ );
+});
+
+add_task(function* test_imagelink() {
+ yield test_contextmenu("#test-image-link",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "---", null,
+ "context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ]
+ );
+});
+
+add_task(function* test_select_input_text() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-select-input-text",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "context-searchselect", true,
+ "---", null,
+ "spell-check-enabled", true
+ ].concat(LOGIN_FILL_ITEMS),
+ {
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let element = doc.querySelector("#test-select-input-text");
+ element.select();
+ });
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_select_input_text_password() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-select-input-text-type-password",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ // spell checker is shown on input[type="password"] on this testcase
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(LOGIN_FILL_ITEMS),
+ {
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let element = doc.querySelector("#test-select-input-text-type-password");
+ element.select();
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ win.getSelection().removeAllRanges();
+ });
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_click_to_play_blocked_plugin() {
+ yield test_contextmenu("#test-plugin",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-ctp-play", true,
+ "context-ctp-hide", true,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {
+ preCheckContextMenuFn: function*() {
+ pushPrefs(["plugins.click_to_play", true]);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+ },
+ postCheckContextMenuFn: function*() {
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ }
+ }
+ );
+});
+
+add_task(function* test_longdesc() {
+ yield test_contextmenu("#test-longdesc",
+ ["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true,
+ "context-viewimagedesc", true
+ ]
+ );
+});
+
+add_task(function* test_srcdoc() {
+ yield test_contextmenu("#test-srcdoc",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "frame", null,
+ ["context-reloadframe", true,
+ "---", null,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ]
+ );
+});
+
+add_task(function* test_input_spell_false() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-contenteditable-spellcheck-false",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ ]
+ );
+ */
+});
+
+const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+
+add_task(function* test_plaintext_sendpagetodevice() {
+ if (!gFxAccounts.sendTabToDeviceEnabled) {
+ return;
+ }
+ const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
+
+ let plainTextItemsWithSendPage =
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-sendpagetodevice", true,
+ ["*Foo", true,
+ "*Bar", true,
+ "---", null,
+ "*All Devices", true], null,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ];
+ yield test_contextmenu("#test-text", plainTextItemsWithSendPage, {
+ *onContextMenuShown() {
+ yield openMenuItemSubmenu("context-sendpagetodevice");
+ }
+ });
+
+ restoreRemoteClients(oldGetter);
+});
+
+add_task(function* test_link_sendlinktodevice() {
+ if (!gFxAccounts.sendTabToDeviceEnabled) {
+ return;
+ }
+ const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
+
+ yield test_contextmenu("#test-link",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true,
+ ["*Foo", true,
+ "*Bar", true,
+ "---", null,
+ "*All Devices", true], null,
+ ],
+ {
+ *onContextMenuShown() {
+ yield openMenuItemSubmenu("context-sendlinktodevice");
+ }
+ });
+
+ restoreRemoteClients(oldGetter);
+});
+
+add_task(function* test_cleanup_html() {
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * Selects the text of the element that matches the provided `selector`
+ *
+ * @param {String} selector
+ * A selector passed to querySelector to find
+ * the element that will be referenced.
+ */
+function* selectText(selector) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, selector, function*(contentSelector) {
+ info(`Selecting text of ${contentSelector}`);
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let div = doc.createRange();
+ let element = doc.querySelector(contentSelector);
+ Assert.ok(element, "Found element to select text from");
+ div.setStartBefore(element);
+ div.setEndAfter(element);
+ win.getSelection().addRange(div);
+ });
+}
diff --git a/browser/base/content/test/general/browser_contextmenu_childprocess.js b/browser/base/content/test/general/browser_contextmenu_childprocess.js
new file mode 100644
index 000000000..3d52be9ab
--- /dev/null
+++ b/browser/base/content/test/general/browser_contextmenu_childprocess.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const gBaseURL = "https://example.com/browser/browser/base/content/test/general/";
+
+add_task(function *() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gBaseURL + "subtst_contextmenu.html");
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+
+ // Get the point of the element with the page menu (test-pagemenu) and
+ // synthesize a right mouse click there.
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("#test-pagemenu", 5, 5, { type : "contextmenu", button : 2 }, tab.linkedBrowser);
+ yield popupShownPromise;
+
+ checkMenu(contextMenu);
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield popupHiddenPromise;
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+function checkItems(menuitem, arr)
+{
+ for (let i = 0; i < arr.length; i += 2) {
+ let str = arr[i];
+ let details = arr[i + 1];
+ if (str == "---") {
+ is(menuitem.localName, "menuseparator", "menuseparator");
+ }
+ else if ("children" in details) {
+ is(menuitem.localName, "menu", "submenu");
+ is(menuitem.getAttribute("label"), str, str + " label");
+ checkItems(menuitem.firstChild.firstChild, details.children);
+ }
+ else {
+ is(menuitem.localName, "menuitem", str + " menuitem");
+
+ is(menuitem.getAttribute("label"), str, str + " label");
+ is(menuitem.getAttribute("type"), details.type, str + " type");
+ is(menuitem.getAttribute("image"), details.icon ? gBaseURL + details.icon : "", str + " icon");
+
+ if (details.checked)
+ is(menuitem.getAttribute("checked"), "true", str + " checked");
+ else
+ ok(!menuitem.hasAttribute("checked"), str + " checked");
+
+ if (details.disabled)
+ is(menuitem.getAttribute("disabled"), "true", str + " disabled");
+ else
+ ok(!menuitem.hasAttribute("disabled"), str + " disabled");
+ }
+
+ menuitem = menuitem.nextSibling;
+ }
+}
+
+function checkMenu(contextMenu)
+{
+ let items = [ "Plain item", {type: "", icon: "", checked: false, disabled: false},
+ "Disabled item", {type: "", icon: "", checked: false, disabled: true},
+ "Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "---", null,
+ "Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
+ "Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Submenu", { children:
+ ["Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}] }
+ ];
+ checkItems(contextMenu.childNodes[2], items);
+}
diff --git a/browser/base/content/test/general/browser_contextmenu_input.js b/browser/base/content/test/general/browser_contextmenu_input.js
new file mode 100644
index 000000000..cfc7b7529
--- /dev/null
+++ b/browser/base/content/test/general/browser_contextmenu_input.js
@@ -0,0 +1,243 @@
+"use strict";
+
+let contextMenu;
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+
+add_task(function* test_setup() {
+ const example_base = "http://example.com/browser/browser/base/content/test/general/";
+ const url = example_base + "subtst_contextmenu_input.html";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+ const contextmenu_common = chrome_base + "contextmenu_common.js";
+ Services.scriptloader.loadSubScript(contextmenu_common, this);
+});
+
+add_task(function* test_text_input() {
+ yield test_contextmenu("#input_text",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", false,
+ "---", null,
+ "spell-check-enabled", true]);
+});
+
+add_task(function* test_text_input_spellcheck() {
+ yield test_contextmenu("#input_spellcheck_no_value",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", false,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null],
+ {
+ waitForSpellCheck: true,
+ // Need to dynamically add/remove the "password" type or LoginManager
+ // will think that the form inputs on the page are part of a login
+ // and will add fill-login context menu items.
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("input_spellcheck_no_value");
+ input.setAttribute("spellcheck", "true");
+ input.clientTop; // force layout flush
+ });
+ },
+ }
+ );
+});
+
+add_task(function* test_text_input_spellcheckwrong() {
+ yield test_contextmenu("#input_spellcheck_incorrect",
+ ["*prodigality", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null],
+ {waitForSpellCheck: true}
+ );
+});
+
+add_task(function* test_text_input_spellcheckcorrect() {
+ yield test_contextmenu("#input_spellcheck_correct",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null],
+ {waitForSpellCheck: true}
+ );
+});
+
+add_task(function* test_text_input_disabled() {
+ yield test_contextmenu("#input_disabled",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true],
+ {skipFocusChange: true}
+ );
+});
+
+add_task(function* test_password_input() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ yield test_contextmenu("#input_password",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null,
+ "---", null,
+ "fill-login", null,
+ ["fill-login-no-logins", false,
+ "---", null,
+ "fill-login-saved-passwords", true], null],
+ {
+ skipFocusChange: true,
+ // Need to dynamically add/remove the "password" type or LoginManager
+ // will think that the form inputs on the page are part of a login
+ // and will add fill-login context menu items.
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("input_password");
+ input.type = "password";
+ input.clientTop; // force layout flush
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("input_password");
+ input.type = "text";
+ input.clientTop; // force layout flush
+ });
+ },
+ }
+ );
+});
+
+add_task(function* test_tel_email_url_number_input() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ for (let selector of ["#input_email", "#input_url", "#input_tel", "#input_number"]) {
+ yield test_contextmenu(selector,
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null],
+ {skipFocusChange: true}
+ );
+ }
+});
+
+add_task(function* test_date_time_color_range_month_week_datetimelocal_input() {
+ for (let selector of ["#input_date", "#input_time", "#input_color",
+ "#input_range", "#input_month", "#input_week",
+ "#input_datetime-local"]) {
+ yield test_contextmenu(selector,
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true],
+ {skipFocusChange: true}
+ );
+ }
+});
+
+add_task(function* test_search_input() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ yield test_contextmenu("#input_search",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null,
+ "---", null,
+ "spell-check-enabled", true],
+ {skipFocusChange: true}
+ );
+});
+
+add_task(function* test_text_input_readonly() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ todo(false, "spell-check should not be enabled for input[readonly]. see bug 1246296");
+ yield test_contextmenu("#input_readonly",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null],
+ {skipFocusChange: true}
+ );
+});
+
+add_task(function* test_cleanup() {
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js b/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js
new file mode 100644
index 000000000..00a06f53e
--- /dev/null
+++ b/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js
@@ -0,0 +1,55 @@
+/*
+ * Description of the Test:
+ * We load an https page which uses a CSP including block-all-mixed-content.
+ * The page tries to load a script over http. We make sure the UI is not
+ * influenced when blocking the mixed content. In particular the page
+ * should still appear fully encrypted with a green lock.
+ */
+
+const PRE_PATH = "https://example.com/browser/browser/base/content/test/general/";
+var gTestBrowser = null;
+
+// ------------------------------------------------------
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+// ------------------------------------------------------
+function verifyUInotDegraded() {
+ // make sure that not mixed content is loaded and also not blocked
+ assertMixedContentBlockingState(
+ gTestBrowser,
+ { activeLoaded: false,
+ activeBlocked: false,
+ passiveLoaded: false
+ }
+ );
+ // clean up and finish test
+ cleanUpAfterTests();
+}
+
+// ------------------------------------------------------
+function runTests() {
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop();
+
+ // Starting the test
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(verifyUInotDegraded);
+ var url = PRE_PATH + "file_csp_block_all_mixedcontent.html";
+ gTestBrowser.loadURI(url);
+}
+
+// ------------------------------------------------------
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ { 'set': [["security.mixed_content.block_active_content", true]] },
+ function() { runTests(); }
+ );
+}
diff --git a/browser/base/content/test/general/browser_ctrlTab.js b/browser/base/content/test/general/browser_ctrlTab.js
new file mode 100644
index 000000000..d16aaeca4
--- /dev/null
+++ b/browser/base/content/test/general/browser_ctrlTab.js
@@ -0,0 +1,185 @@
+add_task(function* () {
+ gPrefService.setBoolPref("browser.ctrlTab.previews", true);
+
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ checkTabs(4);
+
+ yield ctrlTabTest([2], 1, 0);
+ yield ctrlTabTest([2, 3, 1], 2, 2);
+ yield ctrlTabTest([], 4, 2);
+
+ {
+ let selectedIndex = gBrowser.tabContainer.selectedIndex;
+ yield pressCtrlTab();
+ yield pressCtrlTab(true);
+ yield releaseCtrl();
+ is(gBrowser.tabContainer.selectedIndex, selectedIndex,
+ "Ctrl+Tab -> Ctrl+Shift+Tab keeps the selected tab");
+ }
+
+ { // test for bug 445369
+ let tabs = gBrowser.tabs.length;
+ yield pressCtrlTab();
+ yield synthesizeCtrlW();
+ is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes one tab");
+ yield releaseCtrl();
+ }
+
+ { // test for bug 667314
+ let tabs = gBrowser.tabs.length;
+ yield pressCtrlTab();
+ yield pressCtrlTab(true);
+ yield synthesizeCtrlW();
+ is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes the selected tab");
+ yield releaseCtrl();
+ }
+
+ gBrowser.addTab();
+ checkTabs(3);
+ yield ctrlTabTest([2, 1, 0], 7, 1);
+
+ { // test for bug 1292049
+ let tabToClose = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:buildconfig");
+ checkTabs(4);
+ selectTabs([0, 1, 2, 3]);
+
+ yield BrowserTestUtils.removeTab(tabToClose);
+ checkTabs(3);
+ undoCloseTab();
+ checkTabs(4);
+ is(gBrowser.tabContainer.selectedIndex, 3, "tab is selected after closing and restoring it");
+
+ yield ctrlTabTest([], 1, 2);
+ }
+
+ { // test for bug 445369
+ checkTabs(4);
+ selectTabs([1, 2, 0]);
+
+ let selectedTab = gBrowser.selectedTab;
+ let tabToRemove = gBrowser.tabs[1];
+
+ yield pressCtrlTab();
+ yield pressCtrlTab();
+ yield synthesizeCtrlW();
+ ok(!tabToRemove.parentNode,
+ "Ctrl+Tab*2 -> Ctrl+W removes the second most recently selected tab");
+
+ yield pressCtrlTab(true);
+ yield pressCtrlTab(true);
+ yield releaseCtrl();
+ ok(selectedTab.selected,
+ "Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab");
+ }
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkTabs(2);
+
+ yield ctrlTabTest([1], 1, 0);
+
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkTabs(1);
+
+ { // test for bug 445768
+ let focusedWindow = document.commandDispatcher.focusedWindow;
+ let eventConsumed = true;
+ let detectKeyEvent = function (event) {
+ eventConsumed = event.defaultPrevented;
+ };
+ document.addEventListener("keypress", detectKeyEvent, false);
+ yield pressCtrlTab();
+ document.removeEventListener("keypress", detectKeyEvent, false);
+ ok(eventConsumed, "Ctrl+Tab consumed by the tabbed browser if one tab is open");
+ is(focusedWindow, document.commandDispatcher.focusedWindow,
+ "Ctrl+Tab doesn't change focus if one tab is open");
+ }
+
+ // cleanup
+ if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
+ gPrefService.clearUserPref("browser.ctrlTab.previews");
+
+ /* private utility functions */
+
+ function* pressCtrlTab(aShiftKey) {
+ let promise;
+ if (!isOpen() && canOpen()) {
+ promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown");
+ } else {
+ promise = BrowserTestUtils.waitForEvent(document, "keyup");
+ }
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
+ return promise;
+ }
+
+ function* releaseCtrl() {
+ let promise;
+ if (isOpen()) {
+ promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden");
+ } else {
+ promise = BrowserTestUtils.waitForEvent(document, "keyup");
+ }
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
+ return promise;
+ }
+
+ function* synthesizeCtrlW() {
+ let promise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose");
+ EventUtils.synthesizeKey("w", { ctrlKey: true });
+ return promise;
+ }
+
+ function isOpen() {
+ return ctrlTab.isOpen;
+ }
+
+ function canOpen() {
+ return gPrefService.getBoolPref("browser.ctrlTab.previews") && gBrowser.tabs.length > 2;
+ }
+
+ function checkTabs(aTabs) {
+ is(gBrowser.tabs.length, aTabs, "number of open tabs should be " + aTabs);
+ }
+
+ function selectTabs(tabs) {
+ tabs.forEach(function (index) {
+ gBrowser.selectedTab = gBrowser.tabs[index];
+ });
+ }
+
+ function* ctrlTabTest(tabsToSelect, tabTimes, expectedIndex) {
+ selectTabs(tabsToSelect);
+
+ var indexStart = gBrowser.tabContainer.selectedIndex;
+ var tabCount = gBrowser.tabs.length;
+ var normalized = tabTimes % tabCount;
+ var where = normalized == 1 ? "back to the previously selected tab" :
+ normalized + " tabs back in most-recently-selected order";
+
+ for (let i = 0; i < tabTimes; i++) {
+ yield pressCtrlTab();
+
+ if (tabCount > 2)
+ is(gBrowser.tabContainer.selectedIndex, indexStart,
+ "Selected tab doesn't change while tabbing");
+ }
+
+ if (tabCount > 2) {
+ ok(isOpen(),
+ "With " + tabCount + " tabs open, Ctrl+Tab opens the preview panel");
+
+ yield releaseCtrl();
+
+ ok(!isOpen(),
+ "Releasing Ctrl closes the preview panel");
+ } else {
+ ok(!isOpen(),
+ "With " + tabCount + " tabs open, Ctrl+Tab doesn't open the preview panel");
+ }
+
+ is(gBrowser.tabContainer.selectedIndex, expectedIndex,
+ "With "+ tabCount +" tabs open and tab " + indexStart
+ + " selected, Ctrl+Tab*" + tabTimes + " goes " + where);
+ }
+});
diff --git a/browser/base/content/test/general/browser_datachoices_notification.js b/browser/base/content/test/general/browser_datachoices_notification.js
new file mode 100644
index 000000000..360728b4c
--- /dev/null
+++ b/browser/base/content/test/general/browser_datachoices_notification.js
@@ -0,0 +1,221 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Pass an empty scope object to the import to prevent "leaked window property"
+// errors in tests.
+var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+var TelemetryReportingPolicy =
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicy;
+
+const PREF_BRANCH = "datareporting.policy.";
+const PREF_BYPASS_NOTIFICATION = PREF_BRANCH + "dataSubmissionPolicyBypassNotification";
+const PREF_CURRENT_POLICY_VERSION = PREF_BRANCH + "currentPolicyVersion";
+const PREF_ACCEPTED_POLICY_VERSION = PREF_BRANCH + "dataSubmissionPolicyAcceptedVersion";
+const PREF_ACCEPTED_POLICY_DATE = PREF_BRANCH + "dataSubmissionPolicyNotifiedTime";
+
+const TEST_POLICY_VERSION = 37;
+
+function fakeShowPolicyTimeout(set, clear) {
+ let reportingPolicy =
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).Policy;
+ reportingPolicy.setShowInfobarTimeout = set;
+ reportingPolicy.clearShowInfobarTimeout = clear;
+}
+
+function sendSessionRestoredNotification() {
+ let reportingPolicyImpl =
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicyImpl;
+ reportingPolicyImpl.observe(null, "sessionstore-windows-restored", null);
+}
+
+/**
+ * Wait for a tick.
+ */
+function promiseNextTick() {
+ return new Promise(resolve => executeSoon(resolve));
+}
+
+/**
+ * Wait for a notification to be shown in a notification box.
+ * @param {Object} aNotificationBox The notification box.
+ * @return {Promise} Resolved when the notification is displayed.
+ */
+function promiseWaitForAlertActive(aNotificationBox) {
+ let deferred = PromiseUtils.defer();
+ aNotificationBox.addEventListener("AlertActive", function onActive() {
+ aNotificationBox.removeEventListener("AlertActive", onActive, true);
+ deferred.resolve();
+ });
+ return deferred.promise;
+}
+
+/**
+ * Wait for a notification to be closed.
+ * @param {Object} aNotification The notification.
+ * @return {Promise} Resolved when the notification is closed.
+ */
+function promiseWaitForNotificationClose(aNotification) {
+ let deferred = PromiseUtils.defer();
+ waitForNotificationClose(aNotification, deferred.resolve);
+ return deferred.promise;
+}
+
+function triggerInfoBar(expectedTimeoutMs) {
+ let showInfobarCallback = null;
+ let timeoutMs = null;
+ fakeShowPolicyTimeout((callback, timeout) => {
+ showInfobarCallback = callback;
+ timeoutMs = timeout;
+ }, () => {});
+ sendSessionRestoredNotification();
+ Assert.ok(!!showInfobarCallback, "Must have a timer callback.");
+ if (expectedTimeoutMs !== undefined) {
+ Assert.equal(timeoutMs, expectedTimeoutMs, "Timeout should match");
+ }
+ showInfobarCallback();
+}
+
+var checkInfobarButton = Task.async(function* (aNotification) {
+ // Check that the button on the data choices infobar does the right thing.
+ let buttons = aNotification.getElementsByTagName("button");
+ Assert.equal(buttons.length, 1, "There is 1 button in the data reporting notification.");
+ let button = buttons[0];
+
+ // Add an observer to ensure the "advanced" pane opened (but don't bother
+ // closing it - we close the entire window when done.)
+ let paneLoadedPromise = promiseTopicObserved("advanced-pane-loaded");
+
+ // Click on the button.
+ button.click();
+
+ // Wait for the preferences panel to open.
+ yield paneLoadedPromise;
+ yield promiseNextTick();
+});
+
+add_task(function* setup() {
+ const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, true);
+ const currentPolicyVersion = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1);
+
+ // Register a cleanup function to reset our preferences.
+ registerCleanupFunction(() => {
+ Preferences.set(PREF_BYPASS_NOTIFICATION, bypassNotification);
+ Preferences.set(PREF_CURRENT_POLICY_VERSION, currentPolicyVersion);
+
+ return closeAllNotifications();
+ });
+
+ // Don't skip the infobar visualisation.
+ Preferences.set(PREF_BYPASS_NOTIFICATION, false);
+ // Set the current policy version.
+ Preferences.set(PREF_CURRENT_POLICY_VERSION, TEST_POLICY_VERSION);
+});
+
+function clearAcceptedPolicy() {
+ // Reset the accepted policy.
+ Preferences.reset(PREF_ACCEPTED_POLICY_VERSION);
+ Preferences.reset(PREF_ACCEPTED_POLICY_DATE);
+}
+
+add_task(function* test_single_window() {
+ clearAcceptedPolicy();
+
+ // Close all the notifications, then try to trigger the data choices infobar.
+ yield closeAllNotifications();
+
+ let notificationBox = document.getElementById("global-notificationbox");
+
+ // Make sure that we have a coherent initial state.
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), 0,
+ "No version should be set on init.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0), 0,
+ "No date should be set on init.");
+ Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(),
+ "User not notified about datareporting policy.");
+
+ let alertShownPromise = promiseWaitForAlertActive(notificationBox);
+ Assert.ok(!TelemetryReportingPolicy.canUpload(),
+ "User should not be allowed to upload.");
+
+ // Wait for the infobar to be displayed.
+ triggerInfoBar(10 * 1000);
+ yield alertShownPromise;
+
+ Assert.equal(notificationBox.allNotifications.length, 1, "Notification Displayed.");
+ Assert.ok(TelemetryReportingPolicy.canUpload(), "User should be allowed to upload now.");
+
+ yield promiseNextTick();
+ let promiseClosed = promiseWaitForNotificationClose(notificationBox.currentNotification);
+ yield checkInfobarButton(notificationBox.currentNotification);
+ yield promiseClosed;
+
+ Assert.equal(notificationBox.allNotifications.length, 0, "No notifications remain.");
+
+ // Check that we are still clear to upload and that the policy data is saved.
+ Assert.ok(TelemetryReportingPolicy.canUpload());
+ Assert.equal(TelemetryReportingPolicy.testIsUserNotified(), true,
+ "User notified about datareporting policy.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), TEST_POLICY_VERSION,
+ "Version pref set.");
+ Assert.greater(parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10), -1,
+ "Date pref set.");
+});
+
+add_task(function* test_multiple_windows() {
+ clearAcceptedPolicy();
+
+ // Close all the notifications, then try to trigger the data choices infobar.
+ yield closeAllNotifications();
+
+ // Ensure we see the notification on all windows and that action on one window
+ // results in dismiss on every window.
+ let otherWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ // Get the notification box for both windows.
+ let notificationBoxes = [
+ document.getElementById("global-notificationbox"),
+ otherWindow.document.getElementById("global-notificationbox")
+ ];
+
+ Assert.ok(notificationBoxes[1], "2nd window has a global notification box.");
+
+ // Make sure that we have a coherent initial state.
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), 0, "No version should be set on init.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0), 0, "No date should be set on init.");
+ Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(), "User not notified about datareporting policy.");
+
+ let showAlertPromises = [
+ promiseWaitForAlertActive(notificationBoxes[0]),
+ promiseWaitForAlertActive(notificationBoxes[1])
+ ];
+
+ Assert.ok(!TelemetryReportingPolicy.canUpload(),
+ "User should not be allowed to upload.");
+
+ // Wait for the infobars.
+ triggerInfoBar(10 * 1000);
+ yield Promise.all(showAlertPromises);
+
+ // Both notification were displayed. Close one and check that both gets closed.
+ let closeAlertPromises = [
+ promiseWaitForNotificationClose(notificationBoxes[0].currentNotification),
+ promiseWaitForNotificationClose(notificationBoxes[1].currentNotification)
+ ];
+ notificationBoxes[0].currentNotification.close();
+ yield Promise.all(closeAlertPromises);
+
+ // Close the second window we opened.
+ yield BrowserTestUtils.closeWindow(otherWindow);
+
+ // Check that we are clear to upload and that the policy data us saved.
+ Assert.ok(TelemetryReportingPolicy.canUpload(), "User should be allowed to upload now.");
+ Assert.equal(TelemetryReportingPolicy.testIsUserNotified(), true,
+ "User notified about datareporting policy.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), TEST_POLICY_VERSION,
+ "Version pref set.");
+ Assert.greater(parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10), -1,
+ "Date pref set.");
+});
diff --git a/browser/base/content/test/general/browser_decoderDoctor.js b/browser/base/content/test/general/browser_decoderDoctor.js
new file mode 100644
index 000000000..a37972160
--- /dev/null
+++ b/browser/base/content/test/general/browser_decoderDoctor.js
@@ -0,0 +1,122 @@
+"use strict";
+
+function* test_decoder_doctor_notification(type, notificationMessage, options) {
+ yield BrowserTestUtils.withNewTab({ gBrowser }, function*(browser) {
+ let awaitNotificationBar =
+ BrowserTestUtils.waitForNotificationBar(gBrowser, browser, "decoder-doctor-notification");
+
+ yield ContentTask.spawn(browser, type, function*(aType) {
+ Services.obs.notifyObservers(content.window,
+ "decoder-doctor-notification",
+ JSON.stringify({type: aType,
+ isSolved: false,
+ decoderDoctorReportId: "test",
+ formats: "test"}));
+ });
+
+ let notification;
+ try {
+ notification = yield awaitNotificationBar;
+ } catch (ex) {
+ ok(false, ex);
+ return;
+ }
+ ok(notification, "Got decoder-doctor-notification notification");
+
+ is(notification.getAttribute("label"), notificationMessage,
+ "notification message should match expectation");
+ let button = notification.childNodes[0];
+ if (options && options.noLearnMoreButton) {
+ ok(!button, "There should not be a Learn More button");
+ return;
+ }
+
+ is(button.getAttribute("label"), gNavigatorBundle.getString("decoder.noCodecs.button"),
+ "notification button should be 'Learn more'");
+ is(button.getAttribute("accesskey"), gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+ "notification button should have accesskey");
+
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ let url = baseURL + ((options && options.sumo) ||
+ "fix-video-audio-problems-firefox-windows");
+ let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ button.click();
+ let sumoTab = yield awaitNewTab;
+ yield BrowserTestUtils.removeTab(sumoTab);
+ });
+}
+
+add_task(function* test_adobe_cdm_not_found() {
+ // This is only sent on Windows.
+ if (AppConstants.platform != "win") {
+ return;
+ }
+
+ let message;
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ message = gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
+ } else {
+ message = gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+
+ yield test_decoder_doctor_notification("adobe-cdm-not-found", message);
+});
+
+add_task(function* test_adobe_cdm_not_activated() {
+ // This is only sent on Windows.
+ if (AppConstants.platform != "win") {
+ return;
+ }
+
+ let message;
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ message = gNavigatorBundle.getString("decoder.noCodecsXP.message");
+ } else {
+ message = gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+
+ yield test_decoder_doctor_notification("adobe-cdm-not-activated", message);
+});
+
+add_task(function* test_platform_decoder_not_found() {
+ // Not sent on Windows XP.
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ return;
+ }
+
+ let message;
+ let isLinux = AppConstants.platform == "linux";
+ if (isLinux) {
+ message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+ } else {
+ message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+ }
+
+ yield test_decoder_doctor_notification("platform-decoder-not-found",
+ message,
+ {noLearnMoreButton: isLinux});
+});
+
+add_task(function* test_cannot_initialize_pulseaudio() {
+ // This is only sent on Linux.
+ if (AppConstants.platform != "linux") {
+ return;
+ }
+
+ let message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
+ yield test_decoder_doctor_notification("cannot-initialize-pulseaudio",
+ message,
+ {sumo: "fix-common-audio-and-video-issues"});
+});
+
+add_task(function* test_unsupported_libavcodec() {
+ // This is only sent on Linux.
+ if (AppConstants.platform != "linux") {
+ return;
+ }
+
+ let message = gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
+ yield test_decoder_doctor_notification("unsupported-libavcodec",
+ message,
+ {noLearnMoreButton: true});
+});
diff --git a/browser/base/content/test/general/browser_devedition.js b/browser/base/content/test/general/browser_devedition.js
new file mode 100644
index 000000000..06ee42e7e
--- /dev/null
+++ b/browser/base/content/test/general/browser_devedition.js
@@ -0,0 +1,129 @@
+/*
+ * Testing changes for Developer Edition theme.
+ * A special stylesheet should be added to the browser.xul document
+ * when the firefox-devedition@mozilla.org lightweight theme
+ * is applied.
+ */
+
+const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes";
+const PREF_DEVTOOLS_THEME = "devtools.theme";
+const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
+
+LightweightThemeManager.clearBuiltInThemes();
+LightweightThemeManager.addBuiltInTheme(dummyLightweightTheme("firefox-devedition@mozilla.org"));
+
+registerCleanupFunction(() => {
+ // Set preferences back to their original values
+ LightweightThemeManager.currentTheme = null;
+ Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
+ Services.prefs.clearUserPref(PREF_LWTHEME_USED_THEMES);
+
+ LightweightThemeManager.currentTheme = null;
+ LightweightThemeManager.clearBuiltInThemes();
+});
+
+add_task(function* startTests() {
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
+
+ info ("Setting the current theme to null");
+ LightweightThemeManager.currentTheme = null;
+ ok (!DevEdition.isStyleSheetEnabled, "There is no devedition style sheet when no lw theme is applied.");
+
+ info ("Adding a lightweight theme.");
+ LightweightThemeManager.currentTheme = dummyLightweightTheme("preview0");
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been removed when a lightweight theme is applied.");
+
+ info ("Applying the devedition lightweight theme.");
+ let onAttributeAdded = waitForBrightTitlebarAttribute();
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been added when the devedition lightweight theme is applied");
+ yield onAttributeAdded;
+ is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
+ "The brighttitlebarforeground attribute is set on the window.");
+
+ info ("Unapplying all themes.");
+ LightweightThemeManager.currentTheme = null;
+ ok (!DevEdition.isStyleSheetEnabled, "There is no devedition style sheet when no lw theme is applied.");
+
+ info ("Applying the devedition lightweight theme.");
+ onAttributeAdded = waitForBrightTitlebarAttribute();
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been added when the devedition lightweight theme is applied");
+ yield onAttributeAdded;
+ ok (document.documentElement.hasAttribute("brighttitlebarforeground"),
+ "The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
+});
+
+add_task(function* testDevtoolsTheme() {
+ info ("Checking stylesheet and :root attributes based on devtools theme.");
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
+ is (document.documentElement.getAttribute("devtoolstheme"), "light",
+ "The documentElement has an attribute based on devtools theme.");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the light devtools theme.");
+ ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
+ "The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
+
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
+ is (document.documentElement.getAttribute("devtoolstheme"), "dark",
+ "The documentElement has an attribute based on devtools theme.");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the dark devtools theme.");
+ is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
+ "The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
+
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "foobar");
+ is (document.documentElement.getAttribute("devtoolstheme"), "light",
+ "The documentElement has 'light' as a default for the devtoolstheme attribute");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the foobar devtools theme.");
+ ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
+ "The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
+});
+
+function dummyLightweightTheme(id) {
+ return {
+ id: id,
+ name: id,
+ headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
+ iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
+ textcolor: "red",
+ accentcolor: "blue"
+ };
+}
+
+add_task(function* testLightweightThemePreview() {
+ info ("Setting devedition to current and the previewing others");
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled.");
+ LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0"));
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is not enabled after a lightweight theme preview.");
+ LightweightThemeManager.resetPreview();
+ LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1"));
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is not enabled after a second lightweight theme preview.");
+ LightweightThemeManager.resetPreview();
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled again after resetting the preview.");
+ LightweightThemeManager.currentTheme = null;
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is gone after removing the current theme.");
+
+ info ("Previewing the devedition theme");
+ LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org"));
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled.");
+ LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2"));
+ LightweightThemeManager.resetPreview();
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is now disabled after resetting the preview.");
+});
+
+// Use a mutation observer to wait for the brighttitlebarforeground
+// attribute to change. Using this instead of waiting for the load
+// event on the DevEdition styleSheet.
+function waitForBrightTitlebarAttribute() {
+ return new Promise((resolve, reject) => {
+ let mutationObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "brighttitlebarforeground") {
+ mutationObserver.disconnect();
+ resolve();
+ }
+ }
+ });
+ mutationObserver.observe(document.documentElement, { attributes: true });
+ });
+}
diff --git a/browser/base/content/test/general/browser_discovery.js b/browser/base/content/test/general/browser_discovery.js
new file mode 100644
index 000000000..23d44c6a9
--- /dev/null
+++ b/browser/base/content/test/general/browser_discovery.js
@@ -0,0 +1,162 @@
+var browser;
+
+function doc() {
+ return browser.contentDocument;
+}
+
+function setHandlerFunc(aResultFunc) {
+ gBrowser.addEventListener("DOMLinkAdded", function (event) {
+ gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, false);
+ executeSoon(aResultFunc);
+ }, false);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ iconDiscovery();
+ }, true);
+ var rootDir = getRootDirectory(gTestPath);
+ content.location = rootDir + "discovery.html";
+}
+
+var iconDiscoveryTests = [
+ { text: "rel icon discovered" },
+ { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
+ { rel: "ICON", text: "rel is case insensitive" },
+ { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" },
+ { href: "moz.png", text: "relative href works" },
+ { href: "notthere.png", text: "404'd icon is removed properly" },
+ { href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" },
+ { type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" }
+];
+
+function runIconDiscoveryTest() {
+ var testCase = iconDiscoveryTests[0];
+ var head = doc().getElementById("linkparent");
+ var hasSrc = gBrowser.getIcon() != null;
+ if (testCase.pass)
+ ok(hasSrc, testCase.text);
+ else
+ ok(!hasSrc, testCase.text);
+
+ head.removeChild(head.getElementsByTagName('link')[0]);
+ iconDiscoveryTests.shift();
+ iconDiscovery(); // Run the next test.
+}
+
+function iconDiscovery() {
+ if (iconDiscoveryTests.length) {
+ setHandlerFunc(runIconDiscoveryTest);
+ gBrowser.setIcon(gBrowser.selectedTab, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+
+ var testCase = iconDiscoveryTests[0];
+ var head = doc().getElementById("linkparent");
+ var link = doc().createElement("link");
+
+ var rootDir = getRootDirectory(gTestPath);
+ var rel = testCase.rel || "icon";
+ var href = testCase.href || rootDir + "moz.png";
+ var type = testCase.type || "image/png";
+ if (testCase.pass == undefined)
+ testCase.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ head.appendChild(link);
+ } else {
+ searchDiscovery();
+ }
+}
+
+var searchDiscoveryTests = [
+ { text: "rel search discovered" },
+ { rel: "SEARCH", text: "rel is case insensitive" },
+ { rel: "-search-", pass: false, text: "rel -search- not discovered" },
+ { rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" },
+ { href: "https://not.mozilla.com", text: "HTTPS ok" },
+ { href: "ftp://not.mozilla.com", text: "FTP ok" },
+ { href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },
+ { href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" },
+ { type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" },
+ { type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" },
+ { type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
+ { type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" },
+ { rel: "search search search", count: 1, text: "only one engine should be added" }
+];
+
+function runSearchDiscoveryTest() {
+ var testCase = searchDiscoveryTests[0];
+ var title = testCase.title || searchDiscoveryTests.length;
+ if (browser.engines) {
+ var hasEngine = (testCase.count) ? (browser.engines[0].title == title &&
+ browser.engines.length == testCase.count) :
+ (browser.engines[0].title == title);
+ ok(hasEngine, testCase.text);
+ browser.engines = null;
+ }
+ else
+ ok(!testCase.pass, testCase.text);
+
+ searchDiscoveryTests.shift();
+ searchDiscovery(); // Run the next test.
+}
+
+// This handler is called twice, once for each added link element.
+// Only want to check once the second link element has been added.
+var ranOnce = false;
+function runMultipleEnginesTestAndFinalize() {
+ if (!ranOnce) {
+ ranOnce = true;
+ return;
+ }
+ ok(browser.engines, "has engines");
+ is(browser.engines.length, 1, "only one engine");
+ is(browser.engines[0].uri, "http://first.mozilla.com/search.xml", "first engine wins");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function searchDiscovery() {
+ let head = doc().getElementById("linkparent");
+
+ if (searchDiscoveryTests.length) {
+ setHandlerFunc(runSearchDiscoveryTest);
+ let testCase = searchDiscoveryTests[0];
+ let link = doc().createElement("link");
+
+ let rel = testCase.rel || "search";
+ let href = testCase.href || "http://so.not.here.mozilla.com/search.xml";
+ let type = testCase.type || "application/opensearchdescription+xml";
+ let title = testCase.title || searchDiscoveryTests.length;
+ if (testCase.pass == undefined)
+ testCase.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ link.title = title;
+ head.appendChild(link);
+ } else {
+ setHandlerFunc(runMultipleEnginesTestAndFinalize);
+ setHandlerFunc(runMultipleEnginesTestAndFinalize);
+ // Test multiple engines with the same title
+ let link = doc().createElement("link");
+ link.rel = "search";
+ link.href = "http://first.mozilla.com/search.xml";
+ link.type = "application/opensearchdescription+xml";
+ link.title = "Test Engine";
+ let link2 = link.cloneNode(false);
+ link2.href = "http://second.mozilla.com/search.xml";
+
+ head.appendChild(link);
+ head.appendChild(link2);
+ }
+}
diff --git a/browser/base/content/test/general/browser_documentnavigation.js b/browser/base/content/test/general/browser_documentnavigation.js
new file mode 100644
index 000000000..eb789d076
--- /dev/null
+++ b/browser/base/content/test/general/browser_documentnavigation.js
@@ -0,0 +1,266 @@
+/*
+ * This test checks that focus is adjusted properly in a browser when pressing F6 and Shift+F6.
+ * There are additional tests in dom/tests/mochitest/chrome/test_focus_docnav.xul which test
+ * non-browser cases.
+ */
+
+var testPage1 = "data:text/html,<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>";
+var testPage2 = "data:text/html,<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>";
+var testPage3 = "data:text/html,<html id='html3'><body id='body3' contenteditable='true'><button id='button3'>Tab 3</button></body></html>";
+
+var fm = Services.focus;
+
+function* expectFocusOnF6(backward, expectedDocument, expectedElement, onContent, desc)
+{
+ let focusChangedInChildResolver = null;
+ let focusPromise = onContent ? new Promise(resolve => focusChangedInChildResolver = resolve) :
+ BrowserTestUtils.waitForEvent(window, "focus", true);
+
+ function focusChangedListener(msg) {
+ let expected = expectedDocument;
+ if (!expectedElement.startsWith("html")) {
+ expected += "," + expectedElement;
+ }
+
+ is(msg.data.details, expected, desc + " child focus matches");
+ focusChangedInChildResolver();
+ }
+
+ if (onContent) {
+ messageManager.addMessageListener("BrowserTest:FocusChanged", focusChangedListener);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { expectedElementId: expectedElement }, function* (arg) {
+ let contentExpectedElement = content.document.getElementById(arg.expectedElementId);
+ if (!contentExpectedElement) {
+ // Element not found, so look in the child frames.
+ for (let f = 0; f < content.frames.length; f++) {
+ if (content.frames[f].document.getElementById(arg.expectedElementId)) {
+ contentExpectedElement = content.frames[f].document;
+ break;
+ }
+ }
+ }
+ else if (contentExpectedElement.localName == "html") {
+ contentExpectedElement = contentExpectedElement.ownerDocument;
+ }
+
+ if (!contentExpectedElement) {
+ sendSyncMessage("BrowserTest:FocusChanged",
+ { details : "expected element " + arg.expectedElementId + " not found" });
+ return;
+ }
+
+ contentExpectedElement.addEventListener("focus", function focusReceived() {
+ contentExpectedElement.removeEventListener("focus", focusReceived, true);
+
+ const contentFM = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ let details = contentFM.focusedWindow.document.documentElement.id;
+ if (contentFM.focusedElement) {
+ details += "," + contentFM.focusedElement.id;
+ }
+
+ sendSyncMessage("BrowserTest:FocusChanged", { details : details });
+ }, true);
+ });
+ }
+
+ EventUtils.synthesizeKey("VK_F6", { shiftKey: backward });
+ yield focusPromise;
+
+ if (typeof expectedElement == "string") {
+ expectedElement = fm.focusedWindow.document.getElementById(expectedElement);
+ }
+
+ if (gMultiProcessBrowser && onContent) {
+ expectedDocument = "main-window";
+ expectedElement = gBrowser.selectedBrowser;
+ }
+
+ is(fm.focusedWindow.document.documentElement.id, expectedDocument, desc + " document matches");
+ is(fm.focusedElement, expectedElement, desc + " element matches");
+
+ if (onContent) {
+ messageManager.removeMessageListener("BrowserTest:FocusChanged", focusChangedListener);
+ }
+}
+
+// Load a page and navigate between it and the chrome window.
+add_task(function* ()
+{
+ let page1Promise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.selectedBrowser.loadURI(testPage1);
+ yield page1Promise;
+
+ // When the urlbar is focused, pressing F6 should focus the root of the content page.
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "basic focus content page");
+
+ // When the content is focused, pressing F6 should focus the urlbar.
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "basic focus content page urlbar");
+
+ // When a button in content is focused, pressing F6 should focus the urlbar.
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "basic focus content page with button focused");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+ return content.document.getElementById("button1").focus();
+ });
+
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "basic focus content page with button focused urlbar");
+
+ // The document root should be focused, not the button
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "basic focus again content page with button focused");
+
+ // Check to ensure that the root element is focused
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+ Assert.ok(content.document.activeElement == content.document.documentElement,
+ "basic focus again content page with button focused child root is focused");
+ });
+});
+
+// Open a second tab. Document focus should skip the background tab.
+add_task(function* ()
+{
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2);
+
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "basic focus content page and second tab urlbar");
+ yield* expectFocusOnF6(false, "html2", "html2",
+ true, "basic focus content page with second tab");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Shift+F6 should navigate backwards. There's only one document here so the effect
+// is the same.
+add_task(function* ()
+{
+ gURLBar.focus();
+ yield* expectFocusOnF6(true, "html1", "html1",
+ true, "back focus content page");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus content page urlbar");
+});
+
+// Open the sidebar and navigate between the sidebar, content and top-level window
+add_task(function* ()
+{
+ let sidebar = document.getElementById("sidebar");
+
+ let loadPromise = BrowserTestUtils.waitForEvent(sidebar, "load", true);
+ SidebarUI.toggle('viewBookmarksSidebar');
+ yield loadPromise;
+
+
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "bookmarksPanel",
+ sidebar.contentDocument.getElementById("search-box").inputField,
+ false, "focus with sidebar open sidebar");
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "focus with sidebar open content");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus with sidebar urlbar");
+
+ // Now go backwards
+ yield* expectFocusOnF6(true, "html1", "html1",
+ true, "back focus with sidebar open content");
+ yield* expectFocusOnF6(true, "bookmarksPanel",
+ sidebar.contentDocument.getElementById("search-box").inputField,
+ false, "back focus with sidebar open sidebar");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus with sidebar urlbar");
+
+ SidebarUI.toggle('viewBookmarksSidebar');
+});
+
+// Navigate when the downloads panel is open
+add_task(function* ()
+{
+ yield pushPrefs(["accessibility.tabfocus", 7]);
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", true);
+ EventUtils.synthesizeMouseAtCenter(document.getElementById("downloads-button"), { });
+ yield popupShownPromise;
+
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "main-window", document.getElementById("downloadsHistory"),
+ false, "focus with downloads panel open panel");
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "focus with downloads panel open");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus downloads panel open urlbar");
+
+ // Now go backwards
+ yield* expectFocusOnF6(true, "html1", "html1",
+ true, "back focus with downloads panel open");
+ yield* expectFocusOnF6(true, "main-window", document.getElementById("downloadsHistory"),
+ false, "back focus with downloads panel open");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus downloads panel open urlbar");
+
+ let downloadsPopup = document.getElementById("downloadsPanel");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(downloadsPopup, "popuphidden", true);
+ downloadsPopup.hidePopup();
+ yield popupHiddenPromise;
+});
+
+// Navigation with a contenteditable body
+add_task(function* ()
+{
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3);
+
+ // The body should be focused when it is editable, not the root.
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "html3", "body3",
+ true, "focus with contenteditable body");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus with contenteditable body urlbar");
+
+ // Now go backwards
+
+ yield* expectFocusOnF6(false, "html3", "body3",
+ true, "back focus with contenteditable body");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "back focus with contenteditable body urlbar");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Navigation with a frameset loaded
+add_task(function* ()
+{
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/base/content/test/general/file_documentnavigation_frameset.html");
+
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "htmlframe1", "htmlframe1",
+ true, "focus on frameset frame 0");
+ yield* expectFocusOnF6(false, "htmlframe2", "htmlframe2",
+ true, "focus on frameset frame 1");
+ yield* expectFocusOnF6(false, "htmlframe3", "htmlframe3",
+ true, "focus on frameset frame 2");
+ yield* expectFocusOnF6(false, "htmlframe4", "htmlframe4",
+ true, "focus on frameset frame 3");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus on frameset frame urlbar");
+
+ yield* expectFocusOnF6(true, "htmlframe4", "htmlframe4",
+ true, "back focus on frameset frame 3");
+ yield* expectFocusOnF6(true, "htmlframe3", "htmlframe3",
+ true, "back focus on frameset frame 2");
+ yield* expectFocusOnF6(true, "htmlframe2", "htmlframe2",
+ true, "back focus on frameset frame 1");
+ yield* expectFocusOnF6(true, "htmlframe1", "htmlframe1",
+ true, "back focus on frameset frame 0");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus on frameset frame urlbar");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// XXXndeakin add tests for browsers inside of panels
diff --git a/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
new file mode 100644
index 000000000..054fb3cc0
--- /dev/null
+++ b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
@@ -0,0 +1,221 @@
+"use strict";
+
+var gMessageManager;
+
+function frameScript() {
+ addMessageListener("Test:RequestFullscreen", () => {
+ content.document.body.requestFullscreen();
+ });
+ addMessageListener("Test:ExitFullscreen", () => {
+ content.document.exitFullscreen();
+ });
+ addMessageListener("Test:QueryFullscreenState", () => {
+ sendAsyncMessage("Test:FullscreenState", {
+ inDOMFullscreen: !!content.document.fullscreenElement,
+ inFullscreen: content.fullScreen
+ });
+ });
+ content.document.addEventListener("fullscreenchange", () => {
+ sendAsyncMessage("Test:FullscreenChanged", {
+ inDOMFullscreen: !!content.document.fullscreenElement,
+ inFullscreen: content.fullScreen
+ });
+ });
+ function waitUntilActive() {
+ let doc = content.document;
+ if (doc.docShell.isActive && doc.hasFocus()) {
+ sendAsyncMessage("Test:Activated");
+ } else {
+ setTimeout(waitUntilActive, 10);
+ }
+ }
+ waitUntilActive();
+}
+
+function listenOneMessage(aMsg, aListener) {
+ function listener({ data }) {
+ gMessageManager.removeMessageListener(aMsg, listener);
+ aListener(data);
+ }
+ gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function listenOneEvent(aEvent, aListener) {
+ function listener(evt) {
+ removeEventListener(aEvent, listener);
+ aListener(evt);
+ }
+ addEventListener(aEvent, listener);
+}
+
+function queryFullscreenState() {
+ return new Promise(resolve => {
+ listenOneMessage("Test:FullscreenState", resolve);
+ gMessageManager.sendAsyncMessage("Test:QueryFullscreenState");
+ });
+}
+
+function captureUnexpectedFullscreenChange() {
+ ok(false, "catched an unexpected fullscreen change");
+}
+
+const FS_CHANGE_DOM = 1 << 0;
+const FS_CHANGE_SIZE = 1 << 1;
+const FS_CHANGE_BOTH = FS_CHANGE_DOM | FS_CHANGE_SIZE;
+
+function waitForFullscreenChanges(aFlags) {
+ return new Promise(resolve => {
+ let fullscreenData = null;
+ let sizemodeChanged = false;
+ function tryResolve() {
+ if ((!(aFlags & FS_CHANGE_DOM) || fullscreenData) &&
+ (!(aFlags & FS_CHANGE_SIZE) || sizemodeChanged)) {
+ if (!fullscreenData) {
+ queryFullscreenState().then(resolve);
+ } else {
+ resolve(fullscreenData);
+ }
+ }
+ }
+ if (aFlags & FS_CHANGE_SIZE) {
+ listenOneEvent("sizemodechange", () => {
+ sizemodeChanged = true;
+ tryResolve();
+ });
+ }
+ if (aFlags & FS_CHANGE_DOM) {
+ gMessageManager.removeMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ listenOneMessage("Test:FullscreenChanged", data => {
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ fullscreenData = data;
+ tryResolve();
+ });
+ }
+ });
+}
+
+var gTests = [
+ {
+ desc: "document method",
+ affectsFullscreenMode: false,
+ exitFunc: () => {
+ gMessageManager.sendAsyncMessage("Test:ExitFullscreen");
+ }
+ },
+ {
+ desc: "escape key",
+ affectsFullscreenMode: false,
+ exitFunc: () => {
+ executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
+ }
+ },
+ {
+ desc: "F11 key",
+ affectsFullscreenMode: true,
+ exitFunc: function () {
+ executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
+ }
+ }
+];
+
+function checkState(expectedStates, contentStates) {
+ is(contentStates.inDOMFullscreen, expectedStates.inDOMFullscreen,
+ "The DOM fullscreen state of the content should match");
+ // TODO window.fullScreen is not updated as soon as the fullscreen
+ // state flips in child process, hence checking it could cause
+ // anonying intermittent failure. As we just want to confirm the
+ // fullscreen state of the browser window, we can just check the
+ // that on the chrome window below.
+ // is(contentStates.inFullscreen, expectedStates.inFullscreen,
+ // "The fullscreen state of the content should match");
+ is(!!document.fullscreenElement, expectedStates.inDOMFullscreen,
+ "The DOM fullscreen state of the chrome should match");
+ is(window.fullScreen, expectedStates.inFullscreen,
+ "The fullscreen state of the chrome should match");
+}
+
+const kPage = "http://example.org/browser/browser/" +
+ "base/content/test/general/dummy_page.html";
+
+add_task(function* () {
+ yield pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]);
+
+ let tab = gBrowser.addTab(kPage);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+
+ registerCleanupFunction(() => {
+ if (browser.contentWindow.fullScreen) {
+ BrowserFullScreen();
+ }
+ gBrowser.removeTab(tab);
+ });
+
+ gMessageManager = browser.messageManager;
+ gMessageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")();", false);
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+
+ // Wait for the document being activated, so that
+ // fullscreen request won't be denied.
+ yield new Promise(resolve => listenOneMessage("Test:Activated", resolve));
+
+ for (let test of gTests) {
+ let contentStates;
+ info("Testing exit DOM fullscreen via " + test.desc);
+
+ contentStates = yield queryFullscreenState();
+ checkState({inDOMFullscreen: false, inFullscreen: false}, contentStates);
+
+ /* DOM fullscreen without fullscreen mode */
+
+ info("> Enter DOM fullscreen");
+ gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_BOTH);
+ checkState({inDOMFullscreen: true, inFullscreen: true}, contentStates);
+
+ info("> Exit DOM fullscreen");
+ test.exitFunc();
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_BOTH);
+ checkState({inDOMFullscreen: false, inFullscreen: false}, contentStates);
+
+ /* DOM fullscreen with fullscreen mode */
+
+ info("> Enter fullscreen mode");
+ // Need to be asynchronous because sizemodechange event could be
+ // dispatched synchronously, which would cause the event listener
+ // miss that event and wait infinitely.
+ executeSoon(() => BrowserFullScreen());
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_SIZE);
+ checkState({inDOMFullscreen: false, inFullscreen: true}, contentStates);
+
+ info("> Enter DOM fullscreen in fullscreen mode");
+ gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_DOM);
+ checkState({inDOMFullscreen: true, inFullscreen: true}, contentStates);
+
+ info("> Exit DOM fullscreen in fullscreen mode");
+ test.exitFunc();
+ contentStates = yield waitForFullscreenChanges(
+ test.affectsFullscreenMode ? FS_CHANGE_BOTH : FS_CHANGE_DOM);
+ checkState({
+ inDOMFullscreen: false,
+ inFullscreen: !test.affectsFullscreenMode
+ }, contentStates);
+
+ /* Cleanup */
+
+ // Exit fullscreen mode if we are still in
+ if (window.fullScreen) {
+ info("> Cleanup");
+ executeSoon(() => BrowserFullScreen());
+ yield waitForFullscreenChanges(FS_CHANGE_SIZE);
+ }
+ }
+});
diff --git a/browser/base/content/test/general/browser_double_close_tab.js b/browser/base/content/test/general/browser_double_close_tab.js
new file mode 100644
index 000000000..29242c3f9
--- /dev/null
+++ b/browser/base/content/test/general/browser_double_close_tab.js
@@ -0,0 +1,80 @@
+"use strict";
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html";
+var testTab;
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+function waitForDialog(callback) {
+ function onTabModalDialogLoaded(node) {
+ Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded");
+ callback(node);
+ }
+
+ // Listen for the dialog being created
+ Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
+}
+
+function waitForDialogDestroyed(node, callback) {
+ // Now listen for the dialog going away again...
+ let observer = new MutationObserver(function(muts) {
+ if (!node.parentNode) {
+ ok(true, "Dialog is gone");
+ done();
+ }
+ });
+ observer.observe(node.parentNode, {childList: true});
+ let failureTimeout = setTimeout(function() {
+ ok(false, "Dialog should have been destroyed");
+ done();
+ }, 10000);
+
+ function done() {
+ clearTimeout(failureTimeout);
+ observer.disconnect();
+ observer = null;
+ callback();
+ }
+}
+
+add_task(function*() {
+ testTab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(testTab, TEST_PAGE);
+ // XXXgijs the reason this has nesting and callbacks rather than promises is
+ // that DOM promises resolve on the next tick. So they're scheduled
+ // in an event queue. So when we spin a new event queue for a modal dialog...
+ // everything gets messed up and the promise's .then callbacks never get
+ // called, despite resolve() being called just fine.
+ yield new Promise(resolveOuter => {
+ waitForDialog(dialogNode => {
+ waitForDialogDestroyed(dialogNode, () => {
+ let doCompletion = () => setTimeout(resolveOuter, 0);
+ info("Now checking if dialog is destroyed");
+ ok(!dialogNode.parentNode, "onbeforeunload dialog should be gone.");
+ if (dialogNode.parentNode) {
+ // Failed to remove onbeforeunload dialog, so do it ourselves:
+ let leaveBtn = dialogNode.ui.button0;
+ waitForDialogDestroyed(dialogNode, doCompletion);
+ EventUtils.synthesizeMouseAtCenter(leaveBtn, {});
+ return;
+ }
+ doCompletion();
+ });
+ // Click again:
+ document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click();
+ });
+ // Click once:
+ document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click();
+ });
+ yield promiseWaitForCondition(() => !testTab.parentNode);
+ ok(!testTab.parentNode, "Tab should be closed completely");
+});
+
+registerCleanupFunction(function() {
+ if (testTab.parentNode) {
+ // Remove the handler, or closing this tab will prove tricky:
+ try {
+ testTab.linkedBrowser.contentWindow.onbeforeunload = null;
+ } catch (ex) {}
+ gBrowser.removeTab(testTab);
+ }
+});
diff --git a/browser/base/content/test/general/browser_drag.js b/browser/base/content/test/general/browser_drag.js
new file mode 100644
index 000000000..64ad19bde
--- /dev/null
+++ b/browser/base/content/test/general/browser_drag.js
@@ -0,0 +1,45 @@
+function test()
+{
+ waitForExplicitFinish();
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // ---- Test dragging the proxy icon ---
+ var value = content.location.href;
+ var urlString = value + "\n" + content.document.title;
+ var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+ var expected = [ [
+ { type : "text/x-moz-url",
+ data : urlString },
+ { type : "text/uri-list",
+ data : value },
+ { type : "text/plain",
+ data : value },
+ { type : "text/html",
+ data : htmlString }
+ ] ];
+ // set the valid attribute so dropping is allowed
+ var oldstate = gURLBar.getAttribute("pageproxystate");
+ gURLBar.setAttribute("pageproxystate", "valid");
+ var dt = EventUtils.synthesizeDragStart(document.getElementById("identity-box"), expected);
+ is(dt, null, "drag on proxy icon");
+ gURLBar.setAttribute("pageproxystate", oldstate);
+ // Now, the identity information panel is opened by the proxy icon click.
+ // We need to close it for next tests.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+
+ // now test dragging onto a tab
+ var tab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ var browser = gBrowser.getBrowserForTab(tab);
+
+ browser.addEventListener("load", function () {
+ is(browser.contentWindow.location, "http://mochi.test:8888/", "drop on tab");
+ gBrowser.removeTab(tab);
+ finish();
+ }, true);
+
+ EventUtils.synthesizeDrop(tab, tab, [[{type: "text/uri-list", data: "http://mochi.test:8888/"}]], "copy", window);
+}
diff --git a/browser/base/content/test/general/browser_duplicateIDs.js b/browser/base/content/test/general/browser_duplicateIDs.js
new file mode 100644
index 000000000..38fc17820
--- /dev/null
+++ b/browser/base/content/test/general/browser_duplicateIDs.js
@@ -0,0 +1,8 @@
+function test() {
+ var ids = {};
+ Array.forEach(document.querySelectorAll("[id]"), function (node) {
+ var id = node.id;
+ ok(!(id in ids), id + " should be unique");
+ ids[id] = null;
+ });
+}
diff --git a/browser/base/content/test/general/browser_e10s_about_process.js b/browser/base/content/test/general/browser_e10s_about_process.js
new file mode 100644
index 000000000..2b4816754
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_about_process.js
@@ -0,0 +1,114 @@
+const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+const CHROME = {
+ id: "cb34538a-d9da-40f3-b61a-069f0b2cb9fb",
+ path: "test-chrome",
+ flags: 0,
+}
+const CANREMOTE = {
+ id: "2480d3e1-9ce4-4b84-8ae3-910b9a95cbb3",
+ path: "test-allowremote",
+ flags: Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD,
+}
+const MUSTREMOTE = {
+ id: "f849cee5-e13e-44d2-981d-0fb3884aaead",
+ path: "test-mustremote",
+ flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD,
+}
+
+const TEST_MODULES = [
+ CHROME,
+ CANREMOTE,
+ MUSTREMOTE
+]
+
+function AboutModule() {
+}
+
+AboutModule.prototype = {
+ newChannel: function(aURI, aLoadInfo) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ getURIFlags: function(aURI) {
+ for (let module of TEST_MODULES) {
+ if (aURI.path.startsWith(module.path)) {
+ return module.flags;
+ }
+ }
+
+ ok(false, "Called getURIFlags for an unknown page " + aURI.spec);
+ return 0;
+ },
+
+ getIndexedDBOriginPostfix: function(aURI) {
+ return null;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule])
+};
+
+var AboutModuleFactory = {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return new AboutModule().QueryInterface(aIID);
+ },
+
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+add_task(function* init() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ for (let module of TEST_MODULES) {
+ registrar.registerFactory(Components.ID(module.id), "",
+ "@mozilla.org/network/protocol/about;1?what=" + module.path,
+ AboutModuleFactory);
+ }
+});
+
+registerCleanupFunction(() => {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ for (let module of TEST_MODULES) {
+ registrar.unregisterFactory(Components.ID(module.id), AboutModuleFactory);
+ }
+});
+
+function test_url(url, chromeResult, contentResult) {
+ is(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS),
+ chromeResult, "Check URL in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS),
+ contentResult, "Check URL in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CHROME_PROCESS),
+ chromeResult, "Check URL with ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CONTENT_PROCESS),
+ contentResult, "Check URL with ref in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CHROME_PROCESS),
+ chromeResult, "Check URL with query in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CONTENT_PROCESS),
+ contentResult, "Check URL with query in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CHROME_PROCESS),
+ chromeResult, "Check URL with query and ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CONTENT_PROCESS),
+ contentResult, "Check URL with query and ref in content process.");
+}
+
+add_task(function* test_chrome() {
+ test_url("about:" + CHROME.path, true, false);
+});
+
+add_task(function* test_any() {
+ test_url("about:" + CANREMOTE.path, true, true);
+});
+
+add_task(function* test_remote() {
+ test_url("about:" + MUSTREMOTE.path, false, true);
+});
diff --git a/browser/base/content/test/general/browser_e10s_chrome_process.js b/browser/base/content/test/general/browser_e10s_chrome_process.js
new file mode 100644
index 000000000..0726447ce
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_chrome_process.js
@@ -0,0 +1,150 @@
+// Returns a function suitable for add_task which loads startURL, runs
+// transitionTask and waits for endURL to load, checking that the URLs were
+// loaded in the correct process.
+function makeTest(name, startURL, startProcessIsRemote, endURL, endProcessIsRemote, transitionTask) {
+ return function*() {
+ info("Running test " + name + ", " + transitionTask.name);
+ let browser = gBrowser.selectedBrowser;
+
+ // In non-e10s nothing should be remote
+ if (!gMultiProcessBrowser) {
+ startProcessIsRemote = false;
+ endProcessIsRemote = false;
+ }
+
+ // Load the initial URL and make sure we are in the right initial process
+ info("Loading initial URL");
+ browser.loadURI(startURL);
+ yield waitForDocLoadComplete();
+
+ is(browser.currentURI.spec, startURL, "Shouldn't have been redirected");
+ is(browser.isRemoteBrowser, startProcessIsRemote, "Should be displayed in the right process");
+
+ let docLoadedPromise = waitForDocLoadComplete();
+ let asyncTask = Task.async(transitionTask);
+ let expectSyncChange = yield asyncTask(browser, endURL);
+ if (expectSyncChange) {
+ is(browser.isRemoteBrowser, endProcessIsRemote, "Should have switched to the right process synchronously");
+ }
+ yield docLoadedPromise;
+
+ is(browser.currentURI.spec, endURL, "Should have made it to the final URL");
+ is(browser.isRemoteBrowser, endProcessIsRemote, "Should be displayed in the right process");
+ }
+}
+
+const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+const PATH = (getRootDirectory(gTestPath) + "test_process_flags_chrome.html").replace("chrome://mochitests", "");
+
+const CHROME = "chrome://mochitests" + PATH;
+const CANREMOTE = "chrome://mochitests-any" + PATH;
+const MUSTREMOTE = "chrome://mochitests-content" + PATH;
+
+add_task(function* init() {
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+});
+
+registerCleanupFunction(() => {
+ gBrowser.removeCurrentTab();
+});
+
+function test_url(url, chromeResult, contentResult) {
+ is(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS),
+ chromeResult, "Check URL in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS),
+ contentResult, "Check URL in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CHROME_PROCESS),
+ chromeResult, "Check URL with ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CONTENT_PROCESS),
+ contentResult, "Check URL with ref in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CHROME_PROCESS),
+ chromeResult, "Check URL with query in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CONTENT_PROCESS),
+ contentResult, "Check URL with query in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CHROME_PROCESS),
+ chromeResult, "Check URL with query and ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CONTENT_PROCESS),
+ contentResult, "Check URL with query and ref in content process.");
+}
+
+add_task(function* test_chrome() {
+ test_url(CHROME, true, false);
+});
+
+add_task(function* test_any() {
+ test_url(CANREMOTE, true, true);
+});
+
+add_task(function* test_remote() {
+ test_url(MUSTREMOTE, false, true);
+});
+
+// The set of page transitions
+var TESTS = [
+ [
+ "chrome -> chrome",
+ CHROME, false,
+ CHROME, false,
+ ],
+ [
+ "chrome -> canremote",
+ CHROME, false,
+ CANREMOTE, false,
+ ],
+ [
+ "chrome -> mustremote",
+ CHROME, false,
+ MUSTREMOTE, true,
+ ],
+ [
+ "remote -> chrome",
+ MUSTREMOTE, true,
+ CHROME, false,
+ ],
+ [
+ "remote -> canremote",
+ MUSTREMOTE, true,
+ CANREMOTE, true,
+ ],
+ [
+ "remote -> mustremote",
+ MUSTREMOTE, true,
+ MUSTREMOTE, true,
+ ],
+];
+
+// The different ways to transition from one page to another
+var TRANSITIONS = [
+// Loads the new page by calling browser.loadURI directly
+function* loadURI(browser, uri) {
+ info("Calling browser.loadURI");
+ yield BrowserTestUtils.loadURI(browser, uri);
+ return true;
+},
+
+// Loads the new page by finding a link with the right href in the document and
+// clicking it
+function* clickLink(browser, uri) {
+ info("Clicking link");
+
+ function frame_script(frameUri) {
+ let link = content.document.querySelector("a[href='" + frameUri + "']");
+ link.click();
+ }
+
+ browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")(" + JSON.stringify(uri) + ");", false);
+
+ return false;
+},
+];
+
+// Creates a set of test tasks, one for each combination of TESTS and TRANSITIONS.
+for (let test of TESTS) {
+ for (let transition of TRANSITIONS) {
+ add_task(makeTest(...test, transition));
+ }
+}
diff --git a/browser/base/content/test/general/browser_e10s_javascript.js b/browser/base/content/test/general/browser_e10s_javascript.js
new file mode 100644
index 000000000..90e847b09
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_javascript.js
@@ -0,0 +1,11 @@
+const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+add_task(function*() {
+ let url = "javascript:dosomething()";
+
+ ok(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS),
+ "Check URL in chrome process.");
+ ok(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS),
+ "Check URL in content process.");
+});
diff --git a/browser/base/content/test/general/browser_e10s_switchbrowser.js b/browser/base/content/test/general/browser_e10s_switchbrowser.js
new file mode 100644
index 000000000..e6134f749
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js
@@ -0,0 +1,261 @@
+requestLongerTimeout(2);
+
+const DUMMY_PATH = "browser/browser/base/content/test/general/dummy_page.html";
+
+const gExpectedHistory = {
+ index: -1,
+ entries: []
+};
+
+function get_remote_history(browser) {
+ function frame_script() {
+ let webNav = docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ let sessionHistory = webNav.sessionHistory;
+ let result = {
+ index: sessionHistory.index,
+ entries: []
+ };
+
+ for (let i = 0; i < sessionHistory.count; i++) {
+ let entry = sessionHistory.getEntryAtIndex(i, false);
+ result.entries.push({
+ uri: entry.URI.spec,
+ title: entry.title
+ });
+ }
+
+ sendAsyncMessage("Test:History", result);
+ }
+
+ return new Promise(resolve => {
+ browser.messageManager.addMessageListener("Test:History", function listener({data}) {
+ browser.messageManager.removeMessageListener("Test:History", listener);
+ resolve(data);
+ });
+
+ browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+ });
+}
+
+var check_history = Task.async(function*() {
+ let sessionHistory = yield get_remote_history(gBrowser.selectedBrowser);
+
+ let count = sessionHistory.entries.length;
+ is(count, gExpectedHistory.entries.length, "Should have the right number of history entries");
+ is(sessionHistory.index, gExpectedHistory.index, "Should have the right history index");
+
+ for (let i = 0; i < count; i++) {
+ let entry = sessionHistory.entries[i];
+ is(entry.uri, gExpectedHistory.entries[i].uri, "Should have the right URI");
+ is(entry.title, gExpectedHistory.entries[i].title, "Should have the right title");
+ }
+});
+
+function clear_history() {
+ gExpectedHistory.index = -1;
+ gExpectedHistory.entries = [];
+}
+
+// Waits for a load and updates the known history
+var waitForLoad = Task.async(function*(uri) {
+ info("Loading " + uri);
+ // Longwinded but this ensures we don't just shortcut to LoadInNewProcess
+ gBrowser.selectedBrowser.webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+
+ yield waitForDocLoadComplete();
+ gExpectedHistory.index++;
+ gExpectedHistory.entries.push({
+ uri: gBrowser.currentURI.spec,
+ title: gBrowser.contentTitle
+ });
+});
+
+// Waits for a load and updates the known history
+var waitForLoadWithFlags = Task.async(function*(uri, flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE) {
+ info("Loading " + uri + " flags = " + flags);
+ gBrowser.selectedBrowser.loadURIWithFlags(uri, flags, null, null, null);
+
+ yield waitForDocLoadComplete();
+ if (!(flags & Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY)) {
+
+ if (flags & Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY) {
+ gExpectedHistory.entries.pop();
+ }
+ else {
+ gExpectedHistory.index++;
+ }
+
+ gExpectedHistory.entries.push({
+ uri: gBrowser.currentURI.spec,
+ title: gBrowser.contentTitle
+ });
+ }
+});
+
+var back = Task.async(function*() {
+ info("Going back");
+ gBrowser.goBack();
+ yield waitForDocLoadComplete();
+ gExpectedHistory.index--;
+});
+
+var forward = Task.async(function*() {
+ info("Going forward");
+ gBrowser.goForward();
+ yield waitForDocLoadComplete();
+ gExpectedHistory.index++;
+});
+
+// Tests that navigating from a page that should be in the remote process and
+// a page that should be in the main process works and retains history
+add_task(function* test_navigation() {
+ let expectedRemote = gMultiProcessBrowser;
+
+ info("1");
+ // Create a tab and load a remote page in it
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ let {permanentKey} = gBrowser.selectedBrowser;
+ yield waitForLoad("http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("2");
+ // Load another page
+ yield waitForLoad("http://example.com/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("3");
+ // Load a non-remote page
+ yield waitForLoad("about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("4");
+ // Load a remote page
+ yield waitForLoad("http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("5");
+ yield back();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("6");
+ yield back();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("7");
+ yield forward();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("8");
+ yield forward();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("9");
+ yield back();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("10");
+ // Load a new remote page, this should replace the last history entry
+ gExpectedHistory.entries.splice(gExpectedHistory.entries.length - 1, 1);
+ yield waitForLoad("http://example.com/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("11");
+ gBrowser.removeCurrentTab();
+ clear_history();
+});
+
+// Tests that calling gBrowser.loadURI or browser.loadURI to load a page in a
+// different process updates the browser synchronously
+add_task(function* test_synchronous() {
+ let expectedRemote = gMultiProcessBrowser;
+
+ info("1");
+ // Create a tab and load a remote page in it
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ let {permanentKey} = gBrowser.selectedBrowser;
+ yield waitForLoad("http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("2");
+ // Load another page
+ info("Loading about:robots");
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ yield waitForDocLoadComplete();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("3");
+ // Load the remote page again
+ info("Loading http://example.org/" + DUMMY_PATH);
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ yield waitForDocLoadComplete();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("4");
+ gBrowser.removeCurrentTab();
+ clear_history();
+});
+
+// Tests that load flags are correctly passed through to the child process with
+// normal loads
+add_task(function* test_loadflags() {
+ let expectedRemote = gMultiProcessBrowser;
+
+ info("1");
+ // Create a tab and load a remote page in it
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ yield waitForLoadWithFlags("about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ yield check_history();
+
+ info("2");
+ // Load a page in the remote process with some custom flags
+ yield waitForLoadWithFlags("http://example.com/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ yield check_history();
+
+ info("3");
+ // Load a non-remote page
+ yield waitForLoadWithFlags("about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ yield check_history();
+
+ info("4");
+ // Load another remote page
+ yield waitForLoadWithFlags("http://example.org/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ yield check_history();
+
+ is(gExpectedHistory.entries.length, 2, "Should end with the right number of history entries");
+
+ info("5");
+ gBrowser.removeCurrentTab();
+ clear_history();
+});
diff --git a/browser/base/content/test/general/browser_favicon_change.js b/browser/base/content/test/general/browser_favicon_change.js
new file mode 100644
index 000000000..f6b0a2a42
--- /dev/null
+++ b/browser/base/content/test/general/browser_favicon_change.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change.html"
+
+add_task(function*() {
+ let extraTab = gBrowser.selectedTab = gBrowser.addTab();
+ extraTab.linkedBrowser.loadURI(TEST_URL);
+ let tabLoaded = BrowserTestUtils.browserLoaded(extraTab.linkedBrowser);
+ let expectedFavicon = "http://example.org/one-icon";
+ let haveChanged = new Promise.defer();
+ let observer = new MutationObserver(function(mutations) {
+ for (let mut of mutations) {
+ if (mut.attributeName != "image") {
+ continue;
+ }
+ let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
+ if (!imageVal) {
+ // The value gets removed because it doesn't load.
+ continue;
+ }
+ is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
+ haveChanged.resolve();
+ }
+ });
+ observer.observe(extraTab, {attributes: true});
+ yield tabLoaded;
+ yield haveChanged.promise;
+ haveChanged = new Promise.defer();
+ expectedFavicon = "http://example.org/other-icon";
+ ContentTask.spawn(extraTab.linkedBrowser, null, function() {
+ let ev = new content.CustomEvent("PleaseChangeFavicon", {});
+ content.dispatchEvent(ev);
+ });
+ yield haveChanged.promise;
+ observer.disconnect();
+ gBrowser.removeTab(extraTab);
+});
+
diff --git a/browser/base/content/test/general/browser_favicon_change_not_in_document.js b/browser/base/content/test/general/browser_favicon_change_not_in_document.js
new file mode 100644
index 000000000..d14a1da32
--- /dev/null
+++ b/browser/base/content/test/general/browser_favicon_change_not_in_document.js
@@ -0,0 +1,34 @@
+"use strict";
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change_not_in_document.html"
+
+add_task(function*() {
+ let extraTab = gBrowser.selectedTab = gBrowser.addTab();
+ let tabLoaded = promiseTabLoaded(extraTab);
+ extraTab.linkedBrowser.loadURI(TEST_URL);
+ let expectedFavicon = "http://example.org/one-icon";
+ let haveChanged = new Promise.defer();
+ let observer = new MutationObserver(function(mutations) {
+ for (let mut of mutations) {
+ if (mut.attributeName != "image") {
+ continue;
+ }
+ let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
+ if (!imageVal) {
+ // The value gets removed because it doesn't load.
+ continue;
+ }
+ is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
+ haveChanged.resolve();
+ }
+ });
+ observer.observe(extraTab, {attributes: true});
+ yield tabLoaded;
+ expectedFavicon = "http://example.org/yet-another-icon";
+ haveChanged = new Promise.defer();
+ yield haveChanged.promise;
+ observer.disconnect();
+ gBrowser.removeTab(extraTab);
+});
+
+
diff --git a/browser/base/content/test/general/browser_feed_discovery.js b/browser/base/content/test/general/browser_feed_discovery.js
new file mode 100644
index 000000000..73dcef755
--- /dev/null
+++ b/browser/base/content/test/general/browser_feed_discovery.js
@@ -0,0 +1,33 @@
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/feed_discovery.html"
+
+/** Test for Bug 377611 **/
+
+add_task(function* () {
+ // Open a new tab.
+ gBrowser.selectedTab = gBrowser.addTab(URL);
+ registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ let discovered = browser.feeds;
+ ok(discovered.length > 0, "some feeds should be discovered");
+
+ let feeds = {};
+ for (let aFeed of discovered) {
+ feeds[aFeed.href] = true;
+ }
+
+ yield ContentTask.spawn(browser, feeds, function* (contentFeeds) {
+ for (let aLink of content.document.getElementsByTagName("link")) {
+ // ignore real stylesheets, and anything without an href property
+ if (aLink.type != "text/css" && aLink.href) {
+ if (/bogus/i.test(aLink.title)) {
+ ok(!contentFeeds[aLink.href], "don't discover " + aLink.href);
+ } else {
+ ok(contentFeeds[aLink.href], "should discover " + aLink.href);
+ }
+ }
+ }
+ });
+})
diff --git a/browser/base/content/test/general/browser_findbarClose.js b/browser/base/content/test/general/browser_findbarClose.js
new file mode 100644
index 000000000..53503073c
--- /dev/null
+++ b/browser/base/content/test/general/browser_findbarClose.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests find bar auto-close behavior
+
+var newTab;
+
+add_task(function* findbar_test() {
+ waitForExplicitFinish();
+ newTab = gBrowser.addTab("about:blank");
+
+ let promise = ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+ yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", false);
+ });
+ newTab.linkedBrowser.loadURI("http://example.com/browser/" +
+ "browser/base/content/test/general/test_bug628179.html");
+ yield promise;
+
+ gFindBar.open();
+
+ yield new ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+ let iframe = content.document.getElementById("iframe");
+ let awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false);
+ iframe.src = "http://example.org/";
+ yield awaitLoad;
+ });
+
+ ok(!gFindBar.hidden, "the Find bar isn't hidden after the location of a " +
+ "subdocument changes");
+
+ gFindBar.close();
+ gBrowser.removeTab(newTab);
+ finish();
+});
+
diff --git a/browser/base/content/test/general/browser_focusonkeydown.js b/browser/base/content/test/general/browser_focusonkeydown.js
new file mode 100644
index 000000000..5b3337203
--- /dev/null
+++ b/browser/base/content/test/general/browser_focusonkeydown.js
@@ -0,0 +1,26 @@
+add_task(function *()
+{
+ let keyUps = 0;
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,<body>");
+
+ gURLBar.focus();
+
+ window.addEventListener("keyup", function countKeyUps(event) {
+ window.removeEventListener("keyup", countKeyUps, true);
+ if (event.originalTarget == gURLBar.inputField) {
+ keyUps++;
+ }
+ }, true);
+
+ gURLBar.addEventListener("keydown", function redirectFocus(event) {
+ gURLBar.removeEventListener("keydown", redirectFocus, true);
+ gBrowser.selectedBrowser.focus();
+ }, true);
+
+ EventUtils.synthesizeKey("v", { });
+
+ is(keyUps, 1, "Key up fired at url bar");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_fullscreen-window-open.js b/browser/base/content/test/general/browser_fullscreen-window-open.js
new file mode 100644
index 000000000..2624b754a
--- /dev/null
+++ b/browser/base/content/test/general/browser_fullscreen-window-open.js
@@ -0,0 +1,347 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const PREF_DISABLE_OPEN_NEW_WINDOW = "browser.link.open_newwindow.disabled_in_fullscreen";
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+const TEST_FILE = "file_fullscreen-window-open.html";
+const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "http://127.0.0.1:8888/");
+
+function test () {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true);
+
+ let newTab = gBrowser.addTab(gHttpTestRoot + TEST_FILE);
+ gBrowser.selectedTab = newTab;
+
+ whenTabLoaded(newTab, function () {
+ // Enter browser fullscreen mode.
+ BrowserFullScreen();
+
+ runNextTest();
+ });
+}
+
+registerCleanupFunction(function() {
+ // Exit browser fullscreen mode.
+ BrowserFullScreen();
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.clearUserPref(PREF_DISABLE_OPEN_NEW_WINDOW);
+});
+
+var gTests = [
+ test_open,
+ test_open_with_size,
+ test_open_with_pos,
+ test_open_with_outerSize,
+ test_open_with_innerSize,
+ test_open_with_dialog,
+ test_open_when_open_new_window_by_pref,
+ test_open_with_pref_to_disable_in_fullscreen,
+ test_open_from_chrome,
+];
+
+function runNextTest () {
+ let testCase = gTests.shift();
+ if (testCase) {
+ executeSoon(testCase);
+ }
+ else {
+ finish();
+ }
+}
+
+
+// Test for window.open() with no feature.
+function test_open() {
+ waitForTabOpen({
+ message: {
+ title: "test_open",
+ param: "",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with width/height.
+function test_open_with_size() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_size",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with top/left.
+function test_open_with_pos() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_pos",
+ param: "top=200,left=200",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with outerWidth/Height.
+function test_open_with_outerSize() {
+ let [outerWidth, outerHeight] = [window.outerWidth, window.outerHeight];
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_outerSize",
+ param: "outerWidth=200,outerHeight=200",
+ },
+ successFn: function () {
+ is(window.outerWidth, outerWidth, "Don't change window.outerWidth.");
+ is(window.outerHeight, outerHeight, "Don't change window.outerHeight.");
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with innerWidth/Height.
+function test_open_with_innerSize() {
+ let [innerWidth, innerHeight] = [window.innerWidth, window.innerHeight];
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_innerSize",
+ param: "innerWidth=200,innerHeight=200",
+ },
+ successFn: function () {
+ is(window.innerWidth, innerWidth, "Don't change window.innerWidth.");
+ is(window.innerHeight, innerHeight, "Don't change window.innerHeight.");
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with dialog.
+function test_open_with_dialog() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_dialog",
+ param: "dialog=yes",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open()
+// when "browser.link.open_newwindow" is nsIBrowserDOMWindow.OPEN_NEWWINDOW
+function test_open_when_open_new_window_by_pref() {
+ const PREF_NAME = "browser.link.open_newwindow";
+ Services.prefs.setIntPref(PREF_NAME, Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW);
+ is(Services.prefs.getIntPref(PREF_NAME), Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW,
+ PREF_NAME + " is nsIBrowserDOMWindow.OPEN_NEWWINDOW at this time");
+
+ waitForTabOpen({
+ message: {
+ title: "test_open_when_open_new_window_by_pref",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {
+ Services.prefs.clearUserPref(PREF_NAME);
+ },
+ });
+}
+
+// Test for the pref, "browser.link.open_newwindow.disabled_in_fullscreen"
+function test_open_with_pref_to_disable_in_fullscreen() {
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, false);
+
+ waitForWindowOpen({
+ message: {
+ title: "test_open_with_pref_disabled_in_fullscreen",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true);
+ },
+ });
+}
+
+
+// Test for window.open() called from chrome context.
+function test_open_from_chrome() {
+ waitForWindowOpenFromChrome({
+ message: {
+ title: "test_open_from_chrome",
+ param: "",
+ },
+ finalizeFn: function () {}
+ });
+}
+
+function waitForTabOpen(aOptions) {
+ let message = aOptions.message;
+
+ if (!message.title) {
+ ok(false, "Can't get message.title.");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onTabOpen = function onTabOpen(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+
+ let tab = aEvent.target;
+ whenTabLoaded(tab, function () {
+ is(tab.linkedBrowser.contentTitle, message.title,
+ "Opened Tab is expected: " + message.title);
+
+ if (aOptions.successFn) {
+ aOptions.successFn();
+ }
+
+ gBrowser.removeTab(tab);
+ finalize();
+ });
+ }
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
+
+ let finalize = function () {
+ aOptions.finalizeFn();
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ const URI = "data:text/html;charset=utf-8,<!DOCTYPE html><html><head><title>"+
+ message.title +
+ "<%2Ftitle><%2Fhead><body><%2Fbody><%2Fhtml>";
+
+ executeWindowOpenInContent({
+ uri: URI,
+ title: message.title,
+ option: message.param,
+ });
+}
+
+
+function waitForWindowOpen(aOptions) {
+ let message = aOptions.message;
+ let url = aOptions.url || "about:blank";
+
+ if (!message.title) {
+ ok(false, "Can't get message.title");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onFinalize = function () {
+ aOptions.finalizeFn();
+
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let listener = new WindowListener(message.title, getBrowserURL(), {
+ onSuccess: aOptions.successFn,
+ onFinalize: onFinalize,
+ });
+ Services.wm.addListener(listener);
+
+ executeWindowOpenInContent({
+ uri: url,
+ title: message.title,
+ option: message.param,
+ });
+}
+
+function executeWindowOpenInContent(aParam) {
+ ContentTask.spawn(gBrowser.selectedBrowser, JSON.stringify(aParam), function* (dataTestParam) {
+ let testElm = content.document.getElementById("test");
+ testElm.setAttribute("data-test-param", dataTestParam);
+ testElm.click();
+ });
+}
+
+function waitForWindowOpenFromChrome(aOptions) {
+ let message = aOptions.message;
+ let url = aOptions.url || "about:blank";
+
+ if (!message.title) {
+ ok(false, "Can't get message.title");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onFinalize = function () {
+ aOptions.finalizeFn();
+
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let listener = new WindowListener(message.title, getBrowserURL(), {
+ onSuccess: aOptions.successFn,
+ onFinalize: onFinalize,
+ });
+ Services.wm.addListener(listener);
+
+ window.open(url, message.title, message.option);
+}
+
+function WindowListener(aTitle, aUrl, aCallBackObj) {
+ this.test_title = aTitle;
+ this.test_url = aUrl;
+ this.callback_onSuccess = aCallBackObj.onSuccess;
+ this.callBack_onFinalize = aCallBackObj.onFinalize;
+}
+WindowListener.prototype = {
+
+ test_title: null,
+ test_url: null,
+ callback_onSuccess: null,
+ callBack_onFinalize: null,
+
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ let onLoad = aEvent => {
+ is(domwindow.document.location.href, this.test_url,
+ "Opened Window is expected: "+ this.test_title);
+ if (this.callback_onSuccess) {
+ this.callback_onSuccess();
+ }
+
+ domwindow.removeEventListener("load", onLoad, true);
+
+ // wait for trasition to fullscreen on OSX Lion later
+ if (isOSX) {
+ setTimeout(function() {
+ domwindow.close();
+ executeSoon(this.callBack_onFinalize);
+ }.bind(this), 3000);
+ }
+ else {
+ domwindow.close();
+ executeSoon(this.callBack_onFinalize);
+ }
+ };
+ domwindow.addEventListener("load", onLoad, true);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediatorListener,
+ Ci.nsISupports]),
+};
diff --git a/browser/base/content/test/general/browser_fxa_migrate.js b/browser/base/content/test/general/browser_fxa_migrate.js
new file mode 100644
index 000000000..2faf9fb10
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_migrate.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const STATE_CHANGED_TOPIC = "fxa-migration:state-changed";
+const NOTIFICATION_TITLE = "fxa-migration";
+
+var imports = {};
+Cu.import("resource://services-sync/FxaMigrator.jsm", imports);
+
+add_task(function* test() {
+ // Fake the state where we saw an EOL notification.
+ Services.obs.notifyObservers(null, STATE_CHANGED_TOPIC, null);
+
+ let notificationBox = document.getElementById("global-notificationbox");
+ Assert.ok(notificationBox.allNotifications.some(n => {
+ return n.getAttribute("value") == NOTIFICATION_TITLE;
+ }), "Disconnect notification should be present");
+});
diff --git a/browser/base/content/test/general/browser_fxa_oauth.html b/browser/base/content/test/general/browser_fxa_oauth.html
new file mode 100644
index 000000000..b31e7ceb4
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_oauth.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>fxa_oauth_test</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ // Note: This intentionally sends an object instead of a string, to ensure both work
+ // (see browser_fxa_oauth_with_keys.html for the other test)
+ detail: {
+ id: "oauth_client_id",
+ message: {
+ command: "oauth_complete",
+ data: {
+ state: "state",
+ code: "code1",
+ closeWindow: "signin",
+ },
+ },
+ },
+ });
+
+ window.dispatchEvent(event);
+ };
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_fxa_oauth.js b/browser/base/content/test/general/browser_fxa_oauth.js
new file mode 100644
index 000000000..1f688bfa8
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_oauth.js
@@ -0,0 +1,327 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null");
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthClient",
+ "resource://gre/modules/FxAccountsOAuthClient.jsm");
+
+const HTTP_PATH = "http://example.com";
+const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_fxa_oauth.html";
+const HTTP_ENDPOINT_WITH_KEYS = "/browser/browser/base/content/test/general/browser_fxa_oauth_with_keys.html";
+
+var gTests = [
+ {
+ desc: "FxA OAuth - should open a new tab, complete OAuth flow",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+ let properURL = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html";
+ let queryStrings = [
+ "action=signin",
+ "client_id=client_id",
+ "scope=",
+ "state=state",
+ "webChannelId=oauth_client_id",
+ ];
+ queryStrings.sort();
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+ Assert.ok(gBrowser.currentURI.spec.split("?")[0], properURL, "Check URL without params");
+ let actualURL = new URL(gBrowser.currentURI.spec);
+ let actualQueryStrings = actualURL.search.substring(1).split("&");
+ actualQueryStrings.sort();
+ Assert.equal(actualQueryStrings.length, queryStrings.length, "Check number of params");
+
+ for (let i = 0; i < queryStrings.length; i++) {
+ Assert.equal(actualQueryStrings[i], queryStrings[i], "Check parameter " + i);
+ }
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ },
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = function(tokenData) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should open a new tab, complete OAuth flow when forcing auth",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+ let properURL = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html";
+ let queryStrings = [
+ "action=force_auth",
+ "client_id=client_id",
+ "scope=",
+ "state=state",
+ "webChannelId=oauth_client_id",
+ "email=test%40invalid.com",
+ ];
+ queryStrings.sort();
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+ Assert.ok(gBrowser.currentURI.spec.split("?")[0], properURL, "Check URL without params");
+
+ let actualURL = new URL(gBrowser.currentURI.spec);
+ let actualQueryStrings = actualURL.search.substring(1).split("&");
+ actualQueryStrings.sort();
+ Assert.equal(actualQueryStrings.length, queryStrings.length, "Check number of params");
+
+ for (let i = 0; i < queryStrings.length; i++) {
+ Assert.equal(actualQueryStrings[i], queryStrings[i], "Check parameter " + i);
+ }
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ action: "force_auth",
+ email: "test@invalid.com"
+ },
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = function(tokenData) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should receive an error when there's a state mismatch",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should have passed in the expected non-matching state value.
+ let queryString = gBrowser.currentURI.spec.split("?")[1];
+ Assert.ok(queryString.indexOf('state=different-state') >= 0);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "different-state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ },
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = reject;
+
+ client.onError = function(err) {
+ Assert.ok(tabOpened);
+ Assert.equal(err.message, "OAuth flow failed. State doesn't match");
+ resolve();
+ };
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should be able to request keys during OAuth flow",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should have asked for keys.
+ let queryString = gBrowser.currentURI.spec.split('?')[1];
+ Assert.ok(queryString.indexOf('keys=true') >= 0);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ keys: true,
+ },
+ authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS
+ });
+
+ client.onComplete = function(tokenData, keys) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ Assert.deepEqual(keys.kAr, {k: "kAr"});
+ Assert.deepEqual(keys.kBr, {k: "kBr"});
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should not receive keys if not explicitly requested",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should not have asked for keys.
+ let queryString = gBrowser.currentURI.spec.split('?')[1];
+ Assert.ok(queryString.indexOf('keys=true') == -1);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH
+ },
+ // This endpoint will cause the completion message to contain keys.
+ authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS
+ });
+
+ client.onComplete = function(tokenData, keys) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ Assert.strictEqual(keys, undefined);
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should receive an error if keys could not be obtained",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should have asked for keys.
+ let queryString = gBrowser.currentURI.spec.split('?')[1];
+ Assert.ok(queryString.indexOf('keys=true') >= 0);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ keys: true,
+ },
+ // This endpoint will cause the completion message not to contain keys.
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = reject;
+
+ client.onError = function(err) {
+ Assert.ok(tabOpened);
+ Assert.equal(err.message, "OAuth flow failed. Keys were not returned");
+ resolve();
+ };
+
+ client.launchWebFlow();
+ });
+ }
+ }
+]; // gTests
+
+function waitForTab(aCallback) {
+ let container = gBrowser.tabContainer;
+ container.addEventListener("TabOpen", function tabOpener(event) {
+ container.removeEventListener("TabOpen", tabOpener, false);
+ gBrowser.addEventListener("load", function listener() {
+ gBrowser.removeEventListener("load", listener, true);
+ let tab = event.target;
+ aCallback(tab);
+ }, true);
+ }, false);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " http://example.com";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ try {
+ for (let testCase of gTests) {
+ info("Running: " + testCase.desc);
+ yield testCase.run();
+ }
+ } finally {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
+ }
+ }).then(finish, ex => {
+ Assert.ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_fxa_oauth_with_keys.html b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html
new file mode 100644
index 000000000..2c28f7088
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>fxa_oauth_test</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ // Note: This intentionally sends a string instead of an object, to ensure both work
+ // (see browser_fxa_oauth.html for the other test)
+ detail: JSON.stringify({
+ id: "oauth_client_id",
+ message: {
+ command: "oauth_complete",
+ data: {
+ state: "state",
+ code: "code1",
+ closeWindow: "signin",
+ // Keys normally contain more information, but this is enough
+ // to keep Loop's tests happy.
+ keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }},
+ },
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ };
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_fxa_web_channel.html b/browser/base/content/test/general/browser_fxa_web_channel.html
new file mode 100644
index 000000000..be5631ff1
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_web_channel.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>fxa_web_channel_test</title>
+</head>
+<body>
+<script>
+ var webChannelId = "account_updates_test";
+
+ window.onload = function() {
+ var testName = window.location.search.replace(/^\?/, "");
+
+ switch (testName) {
+ case "profile_change":
+ test_profile_change();
+ break;
+ case "login":
+ test_login();
+ break;
+ case "can_link_account":
+ test_can_link_account();
+ break;
+ case "logout":
+ test_logout();
+ break;
+ case "delete":
+ test_delete();
+ break;
+ }
+ };
+
+ function test_profile_change() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "profile:change",
+ data: {
+ uid: "abc123",
+ },
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_login() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:login",
+ data: {
+ authAt: Date.now(),
+ email: "testuser@testuser.com",
+ keyFetchToken: 'key_fetch_token',
+ sessionToken: 'session_token',
+ uid: 'uid',
+ unwrapBKey: 'unwrap_b_key',
+ verified: true,
+ },
+ messageId: 1,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_can_link_account() {
+ window.addEventListener("WebChannelMessageToContent", function (e) {
+ // echo any responses from the browser back to the tests on the
+ // fxaccounts_webchannel_response_echo WebChannel. The tests are
+ // listening for events and do the appropriate checks.
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: 'fxaccounts_webchannel_response_echo',
+ message: e.detail.message,
+ })
+ });
+
+ window.dispatchEvent(event);
+ }, true);
+
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:can_link_account",
+ data: {
+ email: "testuser@testuser.com",
+ },
+ messageId: 2,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_logout() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:logout",
+ data: {
+ uid: 'uid'
+ },
+ messageId: 3,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_delete() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:delete",
+ data: {
+ uid: 'uid'
+ },
+ messageId: 4,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_fxa_web_channel.js b/browser/base/content/test/general/browser_fxa_web_channel.js
new file mode 100644
index 000000000..eb0167ffb
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_web_channel.js
@@ -0,0 +1,210 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
+ return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+ "resource://gre/modules/WebChannel.jsm");
+
+// FxAccountsWebChannel isn't explicitly exported by FxAccountsWebChannel.jsm
+// but we can get it here via a backstage pass.
+var {FxAccountsWebChannel} = Components.utils.import("resource://gre/modules/FxAccountsWebChannel.jsm", {});
+
+const TEST_HTTP_PATH = "http://example.com";
+const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/general/browser_fxa_web_channel.html";
+const TEST_CHANNEL_ID = "account_updates_test";
+
+var gTests = [
+ {
+ desc: "FxA Web Channel - should receive message about profile changes",
+ run: function* () {
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ });
+ let promiseObserver = new Promise((resolve, reject) => {
+ makeObserver(FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
+ Assert.equal(data, "abc123");
+ client.tearDown();
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?profile_change"
+ }, function* () {
+ yield promiseObserver;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - login messages should notify the fxAccounts object",
+ run: function* () {
+
+ let promiseLogin = new Promise((resolve, reject) => {
+ let login = (accountData) => {
+ Assert.equal(typeof accountData.authAt, 'number');
+ Assert.equal(accountData.email, 'testuser@testuser.com');
+ Assert.equal(accountData.keyFetchToken, 'key_fetch_token');
+ Assert.equal(accountData.sessionToken, 'session_token');
+ Assert.equal(accountData.uid, 'uid');
+ Assert.equal(accountData.unwrapBKey, 'unwrap_b_key');
+ Assert.equal(accountData.verified, true);
+
+ client.tearDown();
+ resolve();
+ };
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ login: login
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?login"
+ }, function* () {
+ yield promiseLogin;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - can_link_account messages should respond",
+ run: function* () {
+ let properUrl = TEST_BASE_URL + "?can_link_account";
+
+ let promiseEcho = new Promise((resolve, reject) => {
+
+ let webChannelOrigin = Services.io.newURI(properUrl, null, null);
+ // responses sent to content are echoed back over the
+ // `fxaccounts_webchannel_response_echo` channel. Ensure the
+ // fxaccounts:can_link_account message is responded to.
+ let echoWebChannel = new WebChannel('fxaccounts_webchannel_response_echo', webChannelOrigin);
+ echoWebChannel.listen((webChannelId, message, target) => {
+ Assert.equal(message.command, 'fxaccounts:can_link_account');
+ Assert.equal(message.messageId, 2);
+ Assert.equal(message.data.ok, true);
+
+ client.tearDown();
+ echoWebChannel.stopListening();
+
+ resolve();
+ });
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ shouldAllowRelink(acctName) {
+ return acctName === 'testuser@testuser.com';
+ }
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: properUrl
+ }, function* () {
+ yield promiseEcho;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - logout messages should notify the fxAccounts object",
+ run: function* () {
+ let promiseLogout = new Promise((resolve, reject) => {
+ let logout = (uid) => {
+ Assert.equal(uid, 'uid');
+
+ client.tearDown();
+ resolve();
+ };
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ logout: logout
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?logout"
+ }, function* () {
+ yield promiseLogout;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - delete messages should notify the fxAccounts object",
+ run: function* () {
+ let promiseDelete = new Promise((resolve, reject) => {
+ let logout = (uid) => {
+ Assert.equal(uid, 'uid');
+
+ client.tearDown();
+ resolve();
+ };
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ logout: logout
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?delete"
+ }, function* () {
+ yield promiseDelete;
+ });
+ }
+ }
+]; // gTests
+
+function makeObserver(aObserveTopic, aObserveFunc) {
+ let callback = function (aSubject, aTopic, aData) {
+ if (aTopic == aObserveTopic) {
+ removeMe();
+ aObserveFunc(aSubject, aTopic, aData);
+ }
+ };
+
+ function removeMe() {
+ Services.obs.removeObserver(callback, aObserveTopic);
+ }
+
+ Services.obs.addObserver(callback, aObserveTopic, false);
+ return removeMe;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info("Running: " + testCase.desc);
+ yield testCase.run();
+ }
+ }).then(finish, ex => {
+ Assert.ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_fxaccounts.js b/browser/base/content/test/general/browser_fxaccounts.js
new file mode 100644
index 000000000..0f68286dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxaccounts.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
+var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+var {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
+var FxAccountsCommon = {};
+Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
+
+const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
+
+// instrument gFxAccounts to send observer notifications when it's done
+// what it does.
+(function() {
+ let unstubs = {}; // The original functions we stub out.
+
+ // The stub functions.
+ let stubs = {
+ updateAppMenuItem: function() {
+ return unstubs['updateAppMenuItem'].call(gFxAccounts).then(() => {
+ Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateAppMenuItem", null);
+ });
+ },
+ // Opening preferences is trickier than it should be as leaks are reported
+ // due to the promises it fires off at load time and there's no clear way to
+ // know when they are done.
+ // So just ensure openPreferences is called rather than whether it opens.
+ openPreferences: function() {
+ Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences", null);
+ }
+ };
+
+ for (let name in stubs) {
+ unstubs[name] = gFxAccounts[name];
+ gFxAccounts[name] = stubs[name];
+ }
+ // and undo our damage at the end.
+ registerCleanupFunction(() => {
+ for (let name in unstubs) {
+ gFxAccounts[name] = unstubs[name];
+ }
+ stubs = unstubs = null;
+ });
+})();
+
+// Other setup/cleanup
+var newTab;
+
+Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
+ TEST_ROOT + "accounts_testRemoteCommands.html");
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
+ Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri");
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* initialize() {
+ // Set a new tab with something other than about:blank, so it doesn't get reused.
+ // We must wait for it to load or the promiseTabOpen() call in the next test
+ // gets confused.
+ newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
+ yield promiseTabLoaded(newTab);
+});
+
+// The elements we care about.
+var panelUILabel = document.getElementById("PanelUI-fxa-label");
+var panelUIStatus = document.getElementById("PanelUI-fxa-status");
+var panelUIFooter = document.getElementById("PanelUI-footer-fxa");
+
+// The tests
+add_task(function* test_nouser() {
+ let user = yield fxAccounts.getSignedInUser();
+ Assert.strictEqual(user, null, "start with no user signed in");
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+ Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, null);
+ yield promiseUpdateDone;
+
+ // Check the world - the FxA footer area is visible as it is offering a signin.
+ Assert.ok(isFooterVisible())
+
+ Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel"));
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"), panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out");
+ Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out");
+
+ let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+ panelUIStatus.click();
+ yield promisePreferencesOpened;
+});
+
+/*
+XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds.
+
+add_task(function* test_unverifiedUser() {
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+ yield setSignedInUser(false); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ // Check the world.
+ Assert.ok(isFooterVisible())
+
+ Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+ let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+ panelUIStatus.click();
+ yield promisePreferencesOpened
+ yield signOut();
+});
+*/
+
+add_task(function* test_verifiedUserEmptyProfile() {
+ // We see 2 updateAppMenuItem() calls - one for the signedInUser and one after
+ // we first fetch the profile. We want them both to fire or we aren't testing
+ // the state we think we are testing.
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
+ gFxAccounts._cachedProfile = null;
+ configureProfileURL({}); // successful but empty profile.
+ yield setSignedInUser(true); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ // Check the world.
+ Assert.ok(isFooterVisible())
+ Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+
+ let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+ panelUIStatus.click();
+ yield promisePreferencesOpened;
+ yield signOut();
+});
+
+add_task(function* test_verifiedUserDisplayName() {
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
+ gFxAccounts._cachedProfile = null;
+ configureProfileURL({ displayName: "Test User Display Name" });
+ yield setSignedInUser(true); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ Assert.ok(isFooterVisible())
+ Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+ yield signOut();
+});
+
+add_task(function* test_verifiedUserProfileFailure() {
+ // profile failure means only one observer fires.
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 1);
+ gFxAccounts._cachedProfile = null;
+ configureProfileURL(null, 500);
+ yield setSignedInUser(true); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ Assert.ok(isFooterVisible())
+ Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+ yield signOut();
+});
+
+// Helpers.
+function isFooterVisible() {
+ let style = window.getComputedStyle(panelUIFooter);
+ return style.getPropertyValue("display") == "flex";
+}
+
+function configureProfileURL(profile, responseStatus = 200) {
+ let responseBody = profile ? JSON.stringify(profile) : "";
+ let url = TEST_ROOT + "fxa_profile_handler.sjs?" +
+ "responseStatus=" + responseStatus +
+ "responseBody=" + responseBody +
+ // This is a bit cheeky - the FxA code will just append "/profile"
+ // to the preference value. We arrange for this to be seen by our
+ // .sjs as part of the query string.
+ "&path=";
+
+ Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url);
+}
+
+function promiseObserver(topic, count = 1) {
+ return new Promise(resolve => {
+ let obs = (aSubject, aTopic, aData) => {
+ if (--count == 0) {
+ Services.obs.removeObserver(obs, aTopic);
+ resolve(aSubject);
+ }
+ }
+ Services.obs.addObserver(obs, topic, false);
+ });
+}
+
+// Stolen from browser_aboutHome.js
+function promiseWaitForEvent(node, type, capturing) {
+ return new Promise((resolve) => {
+ node.addEventListener(type, function listener(event) {
+ node.removeEventListener(type, listener, capturing);
+ resolve(event);
+ }, capturing);
+ });
+}
+
+var promiseTabOpen = Task.async(function*(urlBase) {
+ info("Waiting for tab to open...");
+ let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
+ let tab = event.target;
+ yield promiseTabLoadEvent(tab);
+ ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase),
+ "Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase);
+ let whenUnloaded = promiseTabUnloaded(tab);
+ gBrowser.removeTab(tab);
+ yield whenUnloaded;
+});
+
+function promiseTabUnloaded(tab)
+{
+ return new Promise(resolve => {
+ info("Wait for tab to unload");
+ function handle(event) {
+ tab.linkedBrowser.removeEventListener("unload", handle, true);
+ info("Got unload event");
+ resolve(event);
+ }
+ tab.linkedBrowser.addEventListener("unload", handle, true, true);
+ });
+}
+
+// FxAccounts helpers.
+function setSignedInUser(verified) {
+ let data = {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: verified,
+
+ oauthTokens: {
+ // a token for the profile server.
+ profile: "key value",
+ }
+ }
+ return fxAccounts.setSignedInUser(data);
+}
+
+var signOut = Task.async(function* () {
+ // This test needs to make sure that any updates for the logout have
+ // completed before starting the next test, or we see the observer
+ // notifications get out of sync.
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+ // we always want a "localOnly" signout here...
+ yield fxAccounts.signOut(true);
+ yield promiseUpdateDone;
+});
diff --git a/browser/base/content/test/general/browser_gZipOfflineChild.js b/browser/base/content/test/general/browser_gZipOfflineChild.js
new file mode 100644
index 000000000..09691bed8
--- /dev/null
+++ b/browser/base/content/test/general/browser_gZipOfflineChild.js
@@ -0,0 +1,80 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/test_offline_gzip.html"
+
+registerCleanupFunction(function() {
+ // Clean up after ourself
+ let uri = Services.io.newURI(URL, null, null);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.perms.removeFromPrincipal(principal, "offline-app");
+ Services.prefs.clearUserPref("offline-apps.allow_by_default");
+});
+
+var cacheCount = 0;
+var intervalID = 0;
+
+//
+// Handle "message" events which are posted from the iframe upon
+// offline cache events.
+//
+function handleMessageEvents(event) {
+ cacheCount++;
+ switch (cacheCount) {
+ case 1:
+ // This is the initial caching off offline data.
+ is(event.data, "oncache", "Child was successfully cached.");
+ // Reload the frame; this will generate an error message
+ // in the case of bug 501422.
+ event.source.location.reload();
+ // Use setInterval to repeatedly call a function which
+ // checks that one of two things has occurred: either
+ // the offline cache is udpated (which means our iframe
+ // successfully reloaded), or the string "error" appears
+ // in the iframe, as in the case of bug 501422.
+ intervalID = setInterval(function() {
+ // Sometimes document.body may not exist, and trying to access
+ // it will throw an exception, so handle this case.
+ try {
+ var bodyInnerHTML = event.source.document.body.innerHTML;
+ }
+ catch (e) {
+ bodyInnerHTML = "";
+ }
+ if (cacheCount == 2 || bodyInnerHTML.includes("error")) {
+ clearInterval(intervalID);
+ is(cacheCount, 2, "frame not reloaded successfully");
+ if (cacheCount != 2) {
+ finish();
+ }
+ }
+ }, 100);
+ break;
+ case 2:
+ is(event.data, "onupdate", "Child was successfully updated.");
+ clearInterval(intervalID);
+ finish();
+ break;
+ default:
+ // how'd we get here?
+ ok(false, "cacheCount not 1 or 2");
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("offline-apps.allow_by_default", true);
+
+ // Open a new tab.
+ gBrowser.selectedTab = gBrowser.addTab(URL);
+ registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ let window = gBrowser.selectedBrowser.contentWindow;
+
+ window.addEventListener("message", handleMessageEvents, false);
+ });
+}
diff --git a/browser/base/content/test/general/browser_gestureSupport.js b/browser/base/content/test/general/browser_gestureSupport.js
new file mode 100644
index 000000000..b31cad31d
--- /dev/null
+++ b/browser/base/content/test/general/browser_gestureSupport.js
@@ -0,0 +1,670 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Simple gestures tests
+//
+// These tests require the ability to disable the fact that the
+// Firefox chrome intentionally prevents "simple gesture" events from
+// reaching web content.
+
+var test_utils;
+var test_commandset;
+var test_prefBranch = "browser.gesture.";
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // Disable the default gestures support during the test
+ gGestureSupport.init(false);
+
+ test_utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ // Run the tests of "simple gesture" events generally
+ test_EnsureConstantsAreDisjoint();
+ test_TestEventListeners();
+ test_TestEventCreation();
+
+ // Reenable the default gestures support. The remaining tests target
+ // the Firefox gesture functionality.
+ gGestureSupport.init(true);
+
+ // Test Firefox's gestures support.
+ test_commandset = document.getElementById("mainCommandSet");
+ test_swipeGestures();
+ test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture");
+ test_thresholdGesture("pinch", "out", "in", "MozMagnifyGesture");
+ test_rotateGestures();
+}
+
+var test_eventCount = 0;
+var test_expectedType;
+var test_expectedDirection;
+var test_expectedDelta;
+var test_expectedModifiers;
+var test_expectedClickCount;
+var test_imageTab;
+
+function test_gestureListener(evt)
+{
+ is(evt.type, test_expectedType,
+ "evt.type (" + evt.type + ") does not match expected value");
+ is(evt.target, test_utils.elementFromPoint(20, 20, false, false),
+ "evt.target (" + evt.target + ") does not match expected value");
+ is(evt.clientX, 20,
+ "evt.clientX (" + evt.clientX + ") does not match expected value");
+ is(evt.clientY, 20,
+ "evt.clientY (" + evt.clientY + ") does not match expected value");
+ isnot(evt.screenX, 0,
+ "evt.screenX (" + evt.screenX + ") does not match expected value");
+ isnot(evt.screenY, 0,
+ "evt.screenY (" + evt.screenY + ") does not match expected value");
+
+ is(evt.direction, test_expectedDirection,
+ "evt.direction (" + evt.direction + ") does not match expected value");
+ is(evt.delta, test_expectedDelta,
+ "evt.delta (" + evt.delta + ") does not match expected value");
+
+ is(evt.shiftKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.SHIFT_MASK) != 0,
+ "evt.shiftKey did not match expected value");
+ is(evt.ctrlKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.CONTROL_MASK) != 0,
+ "evt.ctrlKey did not match expected value");
+ is(evt.altKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.ALT_MASK) != 0,
+ "evt.altKey did not match expected value");
+ is(evt.metaKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.META_MASK) != 0,
+ "evt.metaKey did not match expected value");
+
+ if (evt.type == "MozTapGesture") {
+ is(evt.clickCount, test_expectedClickCount, "evt.clickCount does not match");
+ }
+
+ test_eventCount++;
+}
+
+function test_helper1(type, direction, delta, modifiers)
+{
+ // Setup the expected values
+ test_expectedType = type;
+ test_expectedDirection = direction;
+ test_expectedDelta = delta;
+ test_expectedModifiers = modifiers;
+
+ let expectedEventCount = test_eventCount + 1;
+
+ document.addEventListener(type, test_gestureListener, true);
+ test_utils.sendSimpleGestureEvent(type, 20, 20, direction, delta, modifiers);
+ document.removeEventListener(type, test_gestureListener, true);
+
+ is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
+}
+
+function test_clicks(type, clicks)
+{
+ // Setup the expected values
+ test_expectedType = type;
+ test_expectedDirection = 0;
+ test_expectedDelta = 0;
+ test_expectedModifiers = 0;
+ test_expectedClickCount = clicks;
+
+ let expectedEventCount = test_eventCount + 1;
+
+ document.addEventListener(type, test_gestureListener, true);
+ test_utils.sendSimpleGestureEvent(type, 20, 20, 0, 0, 0, clicks);
+ document.removeEventListener(type, test_gestureListener, true);
+
+ is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
+}
+
+function test_TestEventListeners()
+{
+ let e = test_helper1; // easier to type this name
+
+ // Swipe gesture animation events
+ e("MozSwipeGestureStart", 0, -0.7, 0);
+ e("MozSwipeGestureUpdate", 0, -0.4, 0);
+ e("MozSwipeGestureEnd", 0, 0, 0);
+ e("MozSwipeGestureStart", 0, 0.6, 0);
+ e("MozSwipeGestureUpdate", 0, 0.3, 0);
+ e("MozSwipeGestureEnd", 0, 1, 0);
+
+ // Swipe gesture event
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_UP, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_DOWN, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+
+ // magnify gesture events
+ e("MozMagnifyGestureStart", 0, 50.0, 0);
+ e("MozMagnifyGestureUpdate", 0, -25.0, 0);
+ e("MozMagnifyGestureUpdate", 0, 5.0, 0);
+ e("MozMagnifyGesture", 0, 30.0, 0);
+
+ // rotate gesture events
+ e("MozRotateGestureStart", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
+ e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE, -13.0, 0);
+ e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_CLOCKWISE, 13.0, 0);
+ e("MozRotateGesture", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
+
+ // Tap and presstap gesture events
+ test_clicks("MozTapGesture", 1);
+ test_clicks("MozTapGesture", 2);
+ test_clicks("MozTapGesture", 3);
+ test_clicks("MozPressTapGesture", 1);
+
+ // simple delivery test for edgeui gestures
+ e("MozEdgeUIStarted", 0, 0, 0);
+ e("MozEdgeUICanceled", 0, 0, 0);
+ e("MozEdgeUICompleted", 0, 0, 0);
+
+ // event.shiftKey
+ let modifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.metaKey
+ modifier = Components.interfaces.nsIDOMEvent.META_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.altKey
+ modifier = Components.interfaces.nsIDOMEvent.ALT_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.ctrlKey
+ modifier = Components.interfaces.nsIDOMEvent.CONTROL_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+}
+
+function test_eventDispatchListener(evt)
+{
+ test_eventCount++;
+ evt.stopPropagation();
+}
+
+function test_helper2(type, direction, delta, altKey, ctrlKey, shiftKey, metaKey)
+{
+ let event = null;
+ let successful;
+
+ try {
+ event = document.createEvent("SimpleGestureEvent");
+ successful = true;
+ }
+ catch (ex) {
+ successful = false;
+ }
+ ok(successful, "Unable to create SimpleGestureEvent");
+
+ try {
+ event.initSimpleGestureEvent(type, true, true, window, 1,
+ 10, 10, 10, 10,
+ ctrlKey, altKey, shiftKey, metaKey,
+ 1, window,
+ 0, direction, delta, 0);
+ successful = true;
+ }
+ catch (ex) {
+ successful = false;
+ }
+ ok(successful, "event.initSimpleGestureEvent should not fail");
+
+ // Make sure the event fields match the expected values
+ is(event.type, type, "Mismatch on evt.type");
+ is(event.direction, direction, "Mismatch on evt.direction");
+ is(event.delta, delta, "Mismatch on evt.delta");
+ is(event.altKey, altKey, "Mismatch on evt.altKey");
+ is(event.ctrlKey, ctrlKey, "Mismatch on evt.ctrlKey");
+ is(event.shiftKey, shiftKey, "Mismatch on evt.shiftKey");
+ is(event.metaKey, metaKey, "Mismatch on evt.metaKey");
+ is(event.view, window, "Mismatch on evt.view");
+ is(event.detail, 1, "Mismatch on evt.detail");
+ is(event.clientX, 10, "Mismatch on evt.clientX");
+ is(event.clientY, 10, "Mismatch on evt.clientY");
+ is(event.screenX, 10, "Mismatch on evt.screenX");
+ is(event.screenY, 10, "Mismatch on evt.screenY");
+ is(event.button, 1, "Mismatch on evt.button");
+ is(event.relatedTarget, window, "Mismatch on evt.relatedTarget");
+
+ // Test event dispatch
+ let expectedEventCount = test_eventCount + 1;
+ document.addEventListener(type, test_eventDispatchListener, true);
+ document.dispatchEvent(event);
+ document.removeEventListener(type, test_eventDispatchListener, true);
+ is(expectedEventCount, test_eventCount, "Dispatched event was never received by listener");
+}
+
+function test_TestEventCreation()
+{
+ // Event creation
+ test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_RIGHT, 20.0,
+ true, false, true, false);
+ test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_LEFT, -20.0,
+ false, true, false, true);
+}
+
+function test_EnsureConstantsAreDisjoint()
+{
+ let up = SimpleGestureEvent.DIRECTION_UP;
+ let down = SimpleGestureEvent.DIRECTION_DOWN;
+ let left = SimpleGestureEvent.DIRECTION_LEFT;
+ let right = SimpleGestureEvent.DIRECTION_RIGHT;
+
+ let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let cclockwise = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ ok(up ^ down, "DIRECTION_UP and DIRECTION_DOWN are not bitwise disjoint");
+ ok(up ^ left, "DIRECTION_UP and DIRECTION_LEFT are not bitwise disjoint");
+ ok(up ^ right, "DIRECTION_UP and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(down ^ left, "DIRECTION_DOWN and DIRECTION_LEFT are not bitwise disjoint");
+ ok(down ^ right, "DIRECTION_DOWN and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(left ^ right, "DIRECTION_LEFT and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(clockwise ^ cclockwise, "ROTATION_CLOCKWISE and ROTATION_COUNTERCLOCKWISE are not bitwise disjoint");
+}
+
+// Helper for test of latched event processing. Emits the actual
+// gesture events to test whether the commands associated with the
+// gesture will only trigger once for each direction of movement.
+function test_emitLatchedEvents(eventPrefix, initialDelta, cmd)
+{
+ let cumulativeDelta = 0;
+ let isIncreasing = initialDelta > 0;
+
+ let expect = {};
+ // Reset the call counters and initialize expected values
+ for (let dir in cmd)
+ cmd[dir].callCount = expect[dir] = 0;
+
+ let check = (aDir, aMsg) => ok(cmd[aDir].callCount == expect[aDir], aMsg);
+ let checkBoth = function(aNum, aInc, aDec) {
+ let prefix = "Step " + aNum + ": ";
+ check("inc", prefix + aInc);
+ check("dec", prefix + aDec);
+ };
+
+ // Send the "Start" event.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, initialDelta, 0);
+ cumulativeDelta += initialDelta;
+ if (isIncreasing) {
+ expect.inc++;
+ checkBoth(1, "Increasing command was not triggered", "Decreasing command was triggered");
+ } else {
+ expect.dec++;
+ checkBoth(1, "Increasing command was triggered", "Decreasing command was not triggered");
+ }
+
+ // Send random values in the same direction and ensure neither
+ // command triggers.
+ for (let i = 0; i < 5; i++) {
+ let delta = Math.random() * (isIncreasing ? 100 : -100);
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
+ cumulativeDelta += delta;
+ checkBoth(2, "Increasing command was triggered", "Decreasing command was triggered");
+ }
+
+ // Now go back in the opposite direction.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
+ - initialDelta, 0);
+ cumulativeDelta += - initialDelta;
+ if (isIncreasing) {
+ expect.dec++;
+ checkBoth(3, "Increasing command was triggered", "Decreasing command was not triggered");
+ } else {
+ expect.inc++;
+ checkBoth(3, "Increasing command was not triggered", "Decreasing command was triggered");
+ }
+
+ // Send random values in the opposite direction and ensure neither
+ // command triggers.
+ for (let i = 0; i < 5; i++) {
+ let delta = Math.random() * (isIncreasing ? -100 : 100);
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
+ cumulativeDelta += delta;
+ checkBoth(4, "Increasing command was triggered", "Decreasing command was triggered");
+ }
+
+ // Go back to the original direction. The original command should trigger.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
+ initialDelta, 0);
+ cumulativeDelta += initialDelta;
+ if (isIncreasing) {
+ expect.inc++;
+ checkBoth(5, "Increasing command was not triggered", "Decreasing command was triggered");
+ } else {
+ expect.dec++;
+ checkBoth(5, "Increasing command was triggered", "Decreasing command was not triggered");
+ }
+
+ // Send the wrap-up event. No commands should be triggered.
+ test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, cumulativeDelta, 0);
+ checkBoth(6, "Increasing command was triggered", "Decreasing command was triggered");
+}
+
+function test_addCommand(prefName, id)
+{
+ let cmd = test_commandset.appendChild(document.createElement("command"));
+ cmd.setAttribute("id", id);
+ cmd.setAttribute("oncommand", "this.callCount++;");
+
+ cmd.origPrefName = prefName;
+ cmd.origPrefValue = gPrefService.getCharPref(prefName);
+ gPrefService.setCharPref(prefName, id);
+
+ return cmd;
+}
+
+function test_removeCommand(cmd)
+{
+ gPrefService.setCharPref(cmd.origPrefName, cmd.origPrefValue);
+ test_commandset.removeChild(cmd);
+}
+
+// Test whether latched events are only called once per direction of motion.
+function test_latchedGesture(gesture, inc, dec, eventPrefix)
+{
+ let branch = test_prefBranch + gesture + ".";
+
+ // Put the gesture into latched mode.
+ let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
+ gPrefService.setBoolPref(branch + "latched", true);
+
+ // Install the test commands for increasing and decreasing motion.
+ let cmd = {
+ inc: test_addCommand(branch + inc, "test:incMotion"),
+ dec: test_addCommand(branch + dec, "test:decMotion"),
+ };
+
+ // Test the gestures in each direction.
+ test_emitLatchedEvents(eventPrefix, 500, cmd);
+ test_emitLatchedEvents(eventPrefix, -500, cmd);
+
+ // Restore the gesture to its original configuration.
+ gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
+ for (let dir in cmd)
+ test_removeCommand(cmd[dir]);
+}
+
+// Test whether non-latched events are triggered upon sufficient motion.
+function test_thresholdGesture(gesture, inc, dec, eventPrefix)
+{
+ let branch = test_prefBranch + gesture + ".";
+
+ // Disable latched mode for this gesture.
+ let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
+ gPrefService.setBoolPref(branch + "latched", false);
+
+ // Set the triggering threshold value to 50.
+ let oldThresholdValue = gPrefService.getIntPref(branch + "threshold");
+ gPrefService.setIntPref(branch + "threshold", 50);
+
+ // Install the test commands for increasing and decreasing motion.
+ let cmdInc = test_addCommand(branch + inc, "test:incMotion");
+ let cmdDec = test_addCommand(branch + dec, "test:decMotion");
+
+ // Send the start event but stop short of triggering threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, 49.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Now trigger the threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, 1, 0);
+ ok(cmdInc.callCount == 1, "Increasing command was not triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // The tracking counter should go to zero. Go back the other way and
+ // stop short of triggering the threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -49.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Now cross the threshold and trigger the decreasing command.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -1.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 1, "Decreasing command was not triggered");
+
+ // Send the wrap-up event. No commands should trigger.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, -0.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Restore the gesture to its original configuration.
+ gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
+ gPrefService.setIntPref(branch + "threshold", oldThresholdValue);
+ test_removeCommand(cmdInc);
+ test_removeCommand(cmdDec);
+}
+
+function test_swipeGestures()
+{
+ // easier to type names for the direction constants
+ let up = SimpleGestureEvent.DIRECTION_UP;
+ let down = SimpleGestureEvent.DIRECTION_DOWN;
+ let left = SimpleGestureEvent.DIRECTION_LEFT;
+ let right = SimpleGestureEvent.DIRECTION_RIGHT;
+
+ let branch = test_prefBranch + "swipe.";
+
+ // Install the test commands for the swipe gestures.
+ let cmdUp = test_addCommand(branch + "up", "test:swipeUp");
+ let cmdDown = test_addCommand(branch + "down", "test:swipeDown");
+ let cmdLeft = test_addCommand(branch + "left", "test:swipeLeft");
+ let cmdRight = test_addCommand(branch + "right", "test:swipeRight");
+
+ function resetCounts() {
+ cmdUp.callCount = 0;
+ cmdDown.callCount = 0;
+ cmdLeft.callCount = 0;
+ cmdRight.callCount = 0;
+ }
+
+ // UP
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, up, 0, 0);
+ ok(cmdUp.callCount == 1, "Step 1: Up command was not triggered");
+ ok(cmdDown.callCount == 0, "Step 1: Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 1: Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 1: Right command was triggered");
+
+ // DOWN
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, down, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 2: Up command was triggered");
+ ok(cmdDown.callCount == 1, "Step 2: Down command was not triggered");
+ ok(cmdLeft.callCount == 0, "Step 2: Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 2: Right command was triggered");
+
+ // LEFT
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, left, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 3: Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 3: Down command was triggered");
+ ok(cmdLeft.callCount == 1, "Step 3: Left command was not triggered");
+ ok(cmdRight.callCount == 0, "Step 3: Right command was triggered");
+
+ // RIGHT
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, right, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 4: Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 4: Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 4: Left command was triggered");
+ ok(cmdRight.callCount == 1, "Step 4: Right command was not triggered");
+
+ // Make sure combinations do not trigger events.
+ let combos = [ up | left, up | right, down | left, down | right];
+ for (let i = 0; i < combos.length; i++) {
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, combos[i], 0, 0);
+ ok(cmdUp.callCount == 0, "Step 5-"+i+": Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 5-"+i+": Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 5-"+i+": Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 5-"+i+": Right command was triggered");
+ }
+
+ // Remove the test commands.
+ test_removeCommand(cmdUp);
+ test_removeCommand(cmdDown);
+ test_removeCommand(cmdLeft);
+ test_removeCommand(cmdRight);
+}
+
+
+function test_rotateHelperGetImageRotation(aImageElement)
+{
+ // Get the true image rotation from the transform matrix, bounded
+ // to 0 <= result < 360
+ let transformValue = content.window.getComputedStyle(aImageElement, null)
+ .transform;
+ if (transformValue == "none")
+ return 0;
+
+ transformValue = transformValue.split("(")[1]
+ .split(")")[0]
+ .split(",");
+ var rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
+ (180 / Math.PI));
+ return (rotation < 0 ? rotation + 360 : rotation);
+}
+
+function test_rotateHelperOneGesture(aImageElement, aCurrentRotation,
+ aDirection, aAmount, aStop)
+{
+ if (aAmount <= 0 || aAmount > 90) // Bound to 0 < aAmount <= 90
+ return;
+
+ // easier to type names for the direction constants
+ let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
+
+ let delta = aAmount * (aDirection == clockwise ? 1 : -1);
+
+ // Kill transition time on image so test isn't wrong and doesn't take 10 seconds
+ aImageElement.style.transitionDuration = "0s";
+
+ // Start the gesture, perform an update, and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, aDirection, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, delta, 0);
+ aImageElement.clientTop;
+
+ // If stop, check intermediate
+ if (aStop) {
+ // Send near-zero-delta to stop, and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, .001, 0);
+ aImageElement.clientTop;
+
+ let stopExpectedRotation = (aCurrentRotation + delta) % 360;
+ if (stopExpectedRotation < 0)
+ stopExpectedRotation += 360;
+
+ is(stopExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
+ "Image rotation at gesture stop/hold: expected=" + stopExpectedRotation +
+ ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
+ ", init=" + aCurrentRotation +
+ ", amt=" + aAmount +
+ ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
+ }
+ // End it and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, aDirection, 0, 0);
+ aImageElement.clientTop;
+
+ let finalExpectedRotation;
+
+ if (aAmount < 45 && aStop) {
+ // Rotate a bit, then stop. Expect no change at end of gesture.
+ finalExpectedRotation = aCurrentRotation;
+ }
+ else {
+ // Either not stopping (expect 90 degree change in aDirection), OR
+ // stopping but after 45, (expect 90 degree change in aDirection)
+ finalExpectedRotation = (aCurrentRotation +
+ (aDirection == clockwise ? 1 : -1) * 90) % 360;
+ if (finalExpectedRotation < 0)
+ finalExpectedRotation += 360;
+ }
+
+ is(finalExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
+ "Image rotation gesture end: expected=" + finalExpectedRotation +
+ ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
+ ", init=" + aCurrentRotation +
+ ", amt=" + aAmount +
+ ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
+}
+
+function test_rotateGesturesOnTab()
+{
+ gBrowser.selectedBrowser.removeEventListener("load", test_rotateGesturesOnTab, true);
+
+ if (!(content.document instanceof ImageDocument)) {
+ ok(false, "Image document failed to open for rotation testing");
+ gBrowser.removeTab(test_imageTab);
+ finish();
+ return;
+ }
+
+ // easier to type names for the direction constants
+ let cl = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let ccl = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ let imgElem = content.document.body &&
+ content.document.body.firstElementChild;
+
+ if (!imgElem) {
+ ok(false, "Could not get image element on ImageDocument for rotation!");
+ gBrowser.removeTab(test_imageTab);
+ finish();
+ return;
+ }
+
+ // Quick function to normalize rotation to 0 <= r < 360
+ var normRot = function(rotation) {
+ rotation = rotation % 360;
+ if (rotation < 0)
+ rotation += 360;
+ return rotation;
+ }
+
+ for (var initRot = 0; initRot < 360; initRot += 90) {
+ // Test each case: at each 90 degree snap; cl/ccl;
+ // amount more or less than 45; stop and hold or don't (32 total tests)
+ // The amount added to the initRot is where it is expected to be
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), cl, 55, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), cl, 55, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), ccl, 55, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), ccl, 55, false);
+
+ // Manually rotate it 90 degrees clockwise to prepare for next iteration,
+ // and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, cl, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, 90, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, cl, 0, 0);
+ imgElem.clientTop;
+ }
+
+ gBrowser.removeTab(test_imageTab);
+ test_imageTab = null;
+ finish();
+}
+
+function test_rotateGestures()
+{
+ test_imageTab = gBrowser.addTab("chrome://branding/content/about-logo.png");
+ gBrowser.selectedTab = test_imageTab;
+
+ gBrowser.selectedBrowser.addEventListener("load", test_rotateGesturesOnTab, true);
+}
diff --git a/browser/base/content/test/general/browser_getshortcutoruri.js b/browser/base/content/test/general/browser_getshortcutoruri.js
new file mode 100644
index 000000000..9ebf8e9ca
--- /dev/null
+++ b/browser/base/content/test/general/browser_getshortcutoruri.js
@@ -0,0 +1,143 @@
+function getPostDataString(aIS) {
+ if (!aIS)
+ return null;
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(aIS);
+ var dataLines = sis.read(aIS.available()).split("\n");
+
+ // only want the last line
+ return dataLines[dataLines.length-1];
+}
+
+function keywordResult(aURL, aPostData, aIsUnsafe) {
+ this.url = aURL;
+ this.postData = aPostData;
+ this.isUnsafe = aIsUnsafe;
+}
+
+function keyWordData() {}
+keyWordData.prototype = {
+ init: function(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.keyword = aKeyWord;
+ this.uri = makeURI(aURL);
+ this.postData = aPostData;
+ this.searchWord = aSearchWord;
+
+ this.method = (this.postData ? "POST" : "GET");
+ }
+}
+
+function bmKeywordData(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.init(aKeyWord, aURL, aPostData, aSearchWord);
+}
+bmKeywordData.prototype = new keyWordData();
+
+function searchKeywordData(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.init(aKeyWord, aURL, aPostData, aSearchWord);
+}
+searchKeywordData.prototype = new keyWordData();
+
+var testData = [
+ [new bmKeywordData("bmget", "http://bmget/search=%s", null, "foo"),
+ new keywordResult("http://bmget/search=foo", null)],
+
+ [new bmKeywordData("bmpost", "http://bmpost/", "search=%s", "foo2"),
+ new keywordResult("http://bmpost/", "search=foo2")],
+
+ [new bmKeywordData("bmpostget", "http://bmpostget/search1=%s", "search2=%s", "foo3"),
+ new keywordResult("http://bmpostget/search1=foo3", "search2=foo3")],
+
+ [new bmKeywordData("bmget-nosearch", "http://bmget-nosearch/", null, ""),
+ new keywordResult("http://bmget-nosearch/", null)],
+
+ [new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"),
+ new keywordResult("http://searchget/?search=foo4", null, true)],
+
+ [new searchKeywordData("searchpost", "http://searchpost/", "search={searchTerms}", "foo5"),
+ new keywordResult("http://searchpost/", "search=foo5", true)],
+
+ [new searchKeywordData("searchpostget", "http://searchpostget/?search1={searchTerms}", "search2={searchTerms}", "foo6"),
+ new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6", true)],
+
+ // Bookmark keywords that don't take parameters should not be activated if a
+ // parameter is passed (bug 420328).
+ [new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"),
+ new keywordResult(null, null, true)],
+ [new bmKeywordData("bmpost-noparam", "http://bmpost-noparam/", "not_a=param", "foo8"),
+ new keywordResult(null, null, true)],
+
+ // Test escaping (%s = escaped, %S = raw)
+ // UTF-8 default
+ [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "foé"),
+ new keywordResult("http://bmget/?esc=fo%C3%A9&raw=foé", null)],
+ // Explicitly-defined ISO-8859-1
+ [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "foé"),
+ new keywordResult("http://bmget/?esc=fo%E9&raw=foé", null)],
+
+ // Bug 359809: Test escaping +, /, and @
+ // UTF-8 default
+ [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "+/@"),
+ new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
+ // Explicitly-defined ISO-8859-1
+ [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"),
+ new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
+
+ // Test using a non-bmKeywordData object, to test the behavior of
+ // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
+ // bmKeywordData objects)
+ [{keyword: "http://gavinsharp.com"},
+ new keywordResult(null, null, true)]
+];
+
+add_task(function* test_getshortcutoruri() {
+ yield setupKeywords();
+
+ for (let item of testData) {
+ let [data, result] = item;
+
+ let query = data.keyword;
+ if (data.searchWord)
+ query += " " + data.searchWord;
+ let returnedData = yield getShortcutOrURIAndPostData(query);
+ // null result.url means we should expect the same query we sent in
+ let expected = result.url || query;
+ is(returnedData.url, expected, "got correct URL for " + data.keyword);
+ is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword);
+ is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
+ }
+
+ yield cleanupKeywords();
+});
+
+var folder = null;
+var gAddedEngines = [];
+
+function* setupKeywords() {
+ folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "keyword-test" });
+ for (let item of testData) {
+ let data = item[0];
+ if (data instanceof bmKeywordData) {
+ yield PlacesUtils.bookmarks.insert({ url: data.uri, parentGuid: folder.guid });
+ yield PlacesUtils.keywords.insert({ keyword: data.keyword, url: data.uri.spec, postData: data.postData });
+ }
+
+ if (data instanceof searchKeywordData) {
+ Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
+ let addedEngine = Services.search.getEngineByName(data.keyword);
+ if (data.postData) {
+ let [paramName, paramValue] = data.postData.split("=");
+ addedEngine.addParam(paramName, paramValue, null);
+ }
+ gAddedEngines.push(addedEngine);
+ }
+ }
+}
+
+function* cleanupKeywords() {
+ PlacesUtils.bookmarks.remove(folder);
+ gAddedEngines.map(Services.search.removeEngine);
+}
diff --git a/browser/base/content/test/general/browser_hide_removing.js b/browser/base/content/test/general/browser_hide_removing.js
new file mode 100644
index 000000000..be62e2d89
--- /dev/null
+++ b/browser/base/content/test/general/browser_hide_removing.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Bug 587922: tabs don't get removed if they're hidden
+
+function test() {
+ waitForExplicitFinish();
+
+ // Add a tab that will get removed and hidden
+ let testTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs");
+ gBrowser.selectedTab = testTab;
+
+ let numVisBeforeHide, numVisAfterHide;
+ gBrowser.tabContainer.addEventListener("TabSelect", function() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false);
+
+ // While the next tab is being selected, hide the removing tab
+ numVisBeforeHide = gBrowser.visibleTabs.length;
+ gBrowser.hideTab(testTab);
+ numVisAfterHide = gBrowser.visibleTabs.length;
+ }, false);
+ gBrowser.removeTab(testTab, {animate: true});
+
+ // Make sure the tab gets removed at the end of the animation by polling
+ (function checkRemoved() {
+ return setTimeout(function() {
+ if (gBrowser.tabs.length != 1) {
+ checkRemoved();
+ return;
+ }
+
+ is(numVisBeforeHide, 1, "animated remove has in 1 tab left");
+ is(numVisAfterHide, 1, "hiding a removing tab is also has 1 tab");
+ finish();
+ }, 50);
+ })();
+}
diff --git a/browser/base/content/test/general/browser_homeDrop.js b/browser/base/content/test/general/browser_homeDrop.js
new file mode 100644
index 000000000..6e87963d5
--- /dev/null
+++ b/browser/base/content/test/general/browser_homeDrop.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function*() {
+ let HOMEPAGE_PREF = "browser.startup.homepage";
+
+ let homepageStr = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ homepageStr.data = "about:mozilla";
+ yield pushPrefs([HOMEPAGE_PREF, homepageStr, Ci.nsISupportsString]);
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // Since synthesizeDrop triggers the srcElement, need to use another button.
+ let dragSrcElement = document.getElementById("downloads-button");
+ ok(dragSrcElement, "Downloads button exists");
+ let homeButton = document.getElementById("home-button");
+ ok(homeButton, "home button present");
+
+ function* drop(dragData, homepage) {
+ let setHomepageDialogPromise = BrowserTestUtils.domWindowOpened();
+
+ EventUtils.synthesizeDrop(dragSrcElement, homeButton, dragData, "copy", window);
+
+ let setHomepageDialog = yield setHomepageDialogPromise;
+ ok(true, "dialog appeared in response to home button drop");
+ yield BrowserTestUtils.waitForEvent(setHomepageDialog, "load", false);
+
+ let setHomepagePromise = new Promise(function(resolve) {
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+ observe: function(subject, topic, data) {
+ is(topic, "nsPref:changed", "observed correct topic");
+ is(data, HOMEPAGE_PREF, "observed correct data");
+ let modified = Services.prefs.getComplexValue(HOMEPAGE_PREF,
+ Ci.nsISupportsString);
+ is(modified.data, homepage, "homepage is set correctly");
+ Services.prefs.removeObserver(HOMEPAGE_PREF, observer);
+
+ Services.prefs.setComplexValue(HOMEPAGE_PREF,
+ Ci.nsISupportsString, homepageStr);
+
+ resolve();
+ }
+ };
+ Services.prefs.addObserver(HOMEPAGE_PREF, observer, false);
+ });
+
+ setHomepageDialog.document.documentElement.acceptDialog();
+
+ yield setHomepagePromise;
+ }
+
+ function dropInvalidURI() {
+ return new Promise(resolve => {
+ let consoleListener = {
+ observe: function (m) {
+ if (m.message.includes("NS_ERROR_DOM_BAD_URI")) {
+ ok(true, "drop was blocked");
+ resolve();
+ }
+ }
+ };
+ Services.console.registerListener(consoleListener);
+ registerCleanupFunction(function () {
+ Services.console.unregisterListener(consoleListener);
+ });
+
+ executeSoon(function () {
+ info("Attempting second drop, of a javascript: URI");
+ // The drop handler throws an exception when dragging URIs that inherit
+ // principal, e.g. javascript:
+ expectUncaughtException();
+ EventUtils.synthesizeDrop(dragSrcElement, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window);
+ });
+ });
+ }
+
+ yield* drop([[{type: "text/plain",
+ data: "http://mochi.test:8888/"}]],
+ "http://mochi.test:8888/");
+ yield* drop([[{type: "text/plain",
+ data: "http://mochi.test:8888/\nhttp://mochi.test:8888/b\nhttp://mochi.test:8888/c"}]],
+ "http://mochi.test:8888/|http://mochi.test:8888/b|http://mochi.test:8888/c");
+ yield dropInvalidURI();
+});
+
diff --git a/browser/base/content/test/general/browser_identity_UI.js b/browser/base/content/test/general/browser_identity_UI.js
new file mode 100644
index 000000000..5aacb2e79
--- /dev/null
+++ b/browser/base/content/test/general/browser_identity_UI.js
@@ -0,0 +1,146 @@
+/* Tests for correct behaviour of getEffectiveHost on identity handler */
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+
+ ok(gIdentityHandler, "gIdentityHandler should exist");
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true).then(() => {
+ gBrowser.selectedBrowser.addEventListener("load", checkResult, true);
+ nextTest();
+ });
+}
+
+// Greek IDN for 'example.test'.
+var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE";
+var tests = [
+ {
+ name: "normal domain",
+ location: "http://test1.example.org/",
+ effectiveHost: "test1.example.org"
+ },
+ {
+ name: "view-source",
+ location: "view-source:http://example.com/",
+ effectiveHost: null
+ },
+ {
+ name: "normal HTTPS",
+ location: "https://example.com/",
+ effectiveHost: "example.com",
+ isHTTPS: true
+ },
+ {
+ name: "IDN subdomain",
+ location: "http://sub1." + idnDomain + "/",
+ effectiveHost: "sub1." + idnDomain
+ },
+ {
+ name: "subdomain with port",
+ location: "http://sub1.test1.example.org:8000/",
+ effectiveHost: "sub1.test1.example.org"
+ },
+ {
+ name: "subdomain HTTPS",
+ location: "https://test1.example.com/",
+ effectiveHost: "test1.example.com",
+ isHTTPS: true
+ },
+ {
+ name: "view-source HTTPS",
+ location: "view-source:https://example.com/",
+ effectiveHost: null,
+ isHTTPS: true
+ },
+ {
+ name: "IP address",
+ location: "http://127.0.0.1:8888/",
+ effectiveHost: "127.0.0.1"
+ },
+]
+
+var gCurrentTest, gCurrentTestIndex = -1, gTestDesc, gPopupHidden;
+// Go through the tests in both directions, to add additional coverage for
+// transitions between different states.
+var gForward = true;
+var gCheckETLD = false;
+function nextTest() {
+ if (!gCheckETLD) {
+ if (gForward)
+ gCurrentTestIndex++;
+ else
+ gCurrentTestIndex--;
+
+ if (gCurrentTestIndex == tests.length) {
+ // Went too far, reverse
+ gCurrentTestIndex--;
+ gForward = false;
+ }
+
+ if (gCurrentTestIndex == -1) {
+ gBrowser.selectedBrowser.removeEventListener("load", checkResult, true);
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+
+ gCurrentTest = tests[gCurrentTestIndex];
+ gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + ")";
+ if (!gForward)
+ gTestDesc += " (second time)";
+ if (gCurrentTest.isHTTPS) {
+ gCheckETLD = true;
+ }
+
+ // Navigate to the next page, which will cause checkResult to fire.
+ let spec = gBrowser.selectedBrowser.currentURI.spec;
+ if (spec == "about:blank" || spec == gCurrentTest.location) {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
+ } else {
+ // Open the Control Center and make sure it closes after nav (Bug 1207542).
+ let popupShown = promisePopupShown(gIdentityHandler._identityPopup);
+ gPopupHidden = promisePopupHidden(gIdentityHandler._identityPopup);
+ gIdentityHandler._identityBox.click();
+ info("Waiting for the Control Center to be shown");
+ popupShown.then(() => {
+ is_element_visible(gIdentityHandler._identityPopup, "Control Center is visible");
+ // Show the subview, which is an easy way in automation to reproduce
+ // Bug 1207542, where the CC wouldn't close on navigation.
+ gBrowser.ownerDocument.querySelector("#identity-popup-security-expander").click();
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
+ });
+ }
+ } else {
+ gCheckETLD = false;
+ gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + " without eTLD in identity icon label)";
+ if (!gForward)
+ gTestDesc += " (second time)";
+ gBrowser.selectedBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY);
+ }
+}
+
+function checkResult() {
+ // Sanity check other values, and the value of gIdentityHandler.getEffectiveHost()
+ is(gIdentityHandler._uri.spec, gCurrentTest.location, "location matches for test " + gTestDesc);
+ // getEffectiveHost can't be called for all modes
+ if (gCurrentTest.effectiveHost === null) {
+ let identityBox = document.getElementById("identity-box");
+ ok(identityBox.className == "unknownIdentity" ||
+ identityBox.className == "chromeUI", "mode matched");
+ } else {
+ is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
+ }
+
+ if (gPopupHidden) {
+ info("Waiting for the Control Center to hide");
+ gPopupHidden.then(() => {
+ gPopupHidden = null;
+ is_element_hidden(gIdentityHandler._identityPopup, "control center is hidden");
+ executeSoon(nextTest);
+ });
+ } else {
+ executeSoon(nextTest);
+ }
+}
diff --git a/browser/base/content/test/general/browser_insecureLoginForms.js b/browser/base/content/test/general/browser_insecureLoginForms.js
new file mode 100644
index 000000000..72db7dbe6
--- /dev/null
+++ b/browser/base/content/test/general/browser_insecureLoginForms.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Load directly from the browser-chrome support files of login tests.
+const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
+
+/**
+ * Waits for the given number of occurrences of InsecureLoginFormsStateChange
+ * on the given browser element.
+ */
+function waitForInsecureLoginFormsStateChange(browser, count) {
+ return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
+ false, () => --count == 0);
+}
+
+/**
+ * Checks the insecure login forms logic for the identity block.
+ */
+add_task(function* test_simple() {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+ "set": [["security.insecure_password.ui.enabled", true]],
+ }, resolve));
+
+ for (let [origin, expectWarning] of [
+ ["http://example.com", true],
+ ["http://127.0.0.1", false],
+ ["https://example.com", false],
+ ]) {
+ let testUrlPath = origin + TEST_URL_PATH;
+ let tab = gBrowser.addTab(testUrlPath + "form_basic.html");
+ let browser = tab.linkedBrowser;
+ yield Promise.all([
+ BrowserTestUtils.switchTab(gBrowser, tab),
+ BrowserTestUtils.browserLoaded(browser),
+ // One event is triggered by pageshow and one by DOMFormHasPassword.
+ waitForInsecureLoginFormsStateChange(browser, 2),
+ ]);
+
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityBox.click();
+ document.getElementById("identity-popup-security-expander").click();
+
+ if (expectWarning) {
+ is_element_visible(document.getElementById("connection-icon"));
+ let connectionIconImage = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("connection-icon"), "")
+ .getPropertyValue("list-style-image");
+ let securityViewBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
+ .getPropertyValue("background-image");
+ let securityContentBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
+ .getPropertyValue("background-image");
+ is(connectionIconImage,
+ "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg#icon\")",
+ "Using expected icon image in the identity block");
+ is(securityViewBG,
+ "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "Using expected icon image in the Control Center main view");
+ is(securityContentBG,
+ "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "Using expected icon image in the Control Center subview");
+ is(Array.filter(document.querySelectorAll("[observes=identity-popup-insecure-login-forms-learn-more]"),
+ element => !is_hidden(element)).length, 1,
+ "The 'Learn more' link should be visible once.");
+ }
+
+ // Messages should be visible when the scheme is HTTP, and invisible when
+ // the scheme is HTTPS.
+ is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
+ element => !is_hidden(element)),
+ expectWarning,
+ "The relevant messages should be visible or hidden.");
+
+ gIdentityHandler._identityPopup.hidden = true;
+ gBrowser.removeTab(tab);
+ }
+});
+
+/**
+ * Checks that the insecure login forms logic does not regress mixed content
+ * blocking messages when mixed active content is loaded.
+ */
+add_task(function* test_mixedcontent() {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+ "set": [["security.mixed_content.block_active_content", false]],
+ }, resolve));
+
+ // Load the page with the subframe in a new tab.
+ let testUrlPath = "://example.com" + TEST_URL_PATH;
+ let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
+ let browser = tab.linkedBrowser;
+ yield Promise.all([
+ BrowserTestUtils.switchTab(gBrowser, tab),
+ BrowserTestUtils.browserLoaded(browser),
+ // Two events are triggered by pageshow and one by DOMFormHasPassword.
+ waitForInsecureLoginFormsStateChange(browser, 3),
+ ]);
+
+ assertMixedContentBlockingState(browser, { activeLoaded: true,
+ activeBlocked: false,
+ passiveLoaded: false });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Checks that insecure window.opener does not trigger a warning.
+ */
+add_task(function* test_ignoring_window_opener() {
+ let newTabURL = "https://example.com" + TEST_URL_PATH + "form_basic.html";
+ let path = getRootDirectory(gTestPath)
+ .replace("chrome://mochitests/content", "http://example.com");
+ let url = path + "insecure_opener.html";
+
+ yield BrowserTestUtils.withNewTab(url, function*(browser) {
+ // Clicking the link will spawn a new tab.
+ let loaded = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL);
+ yield ContentTask.spawn(browser, {}, function() {
+ content.document.getElementById("link").click();
+ });
+ let tab = yield loaded;
+ browser = tab.linkedBrowser;
+ yield waitForInsecureLoginFormsStateChange(browser, 2);
+
+ // Open the identity popup.
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityBox.click();
+ document.getElementById("identity-popup-security-expander").click();
+
+ ok(is_visible(document.getElementById("connection-icon")),
+ "Connection icon is visible");
+
+ // Assert that the identity indicators are still "secure".
+ let connectionIconImage = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("connection-icon"))
+ .getPropertyValue("list-style-image");
+ let securityViewBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-securityView"))
+ .getPropertyValue("background-image");
+ let securityContentBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-security-content"))
+ .getPropertyValue("background-image");
+ is(connectionIconImage,
+ "url(\"chrome://browser/skin/connection-secure.svg\")",
+ "Using expected icon image in the identity block");
+ is(securityViewBG,
+ "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "Using expected icon image in the Control Center main view");
+ is(securityContentBG,
+ "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "Using expected icon image in the Control Center subview");
+
+ ok(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
+ element => is_hidden(element)),
+ "All messages should be hidden.");
+
+ gIdentityHandler._identityPopup.hidden = true;
+
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js
new file mode 100644
index 000000000..8e69e781b
--- /dev/null
+++ b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js
@@ -0,0 +1,39 @@
+"use strict";
+
+
+/**
+ * Verify that loading an invalid URI does not clobber a previously-loaded page's history
+ * entry, but that the invalid URI gets its own history entry instead. We're checking this
+ * using nsIWebNavigation's canGoBack, as well as actually going back and then checking
+ * canGoForward.
+ */
+add_task(function* checkBackFromInvalidURI() {
+ yield pushPrefs(["keyword.enabled", false]);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots", true);
+ gURLBar.value = "::2600";
+ gURLBar.focus();
+
+ let promiseErrorPageLoaded = new Promise(resolve => {
+ tab.linkedBrowser.addEventListener("DOMContentLoaded", function onLoad() {
+ tab.linkedBrowser.removeEventListener("DOMContentLoaded", onLoad, false, true);
+ resolve();
+ }, false, true);
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield promiseErrorPageLoaded;
+
+ ok(gBrowser.webNavigation.canGoBack, "Should be able to go back");
+ if (gBrowser.webNavigation.canGoBack) {
+ // Can't use DOMContentLoaded here because the page is bfcached. Can't use pageshow for
+ // the error page because it doesn't seem to fire for those.
+ let promiseOtherPageLoaded = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "pageshow", false,
+ // Be paranoid we *are* actually seeing this other page load, not some kind of race
+ // for if/when we do start firing pageshow for the error page...
+ function(e) { return gBrowser.currentURI.spec == "about:robots" }
+ );
+ gBrowser.goBack();
+ yield promiseOtherPageLoaded;
+ ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward from previous page.");
+ }
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_keywordBookmarklets.js b/browser/base/content/test/general/browser_keywordBookmarklets.js
new file mode 100644
index 000000000..5e94733fe
--- /dev/null
+++ b/browser/base/content/test/general/browser_keywordBookmarklets.js
@@ -0,0 +1,54 @@
+"use strict"
+
+add_task(function* test_keyword_bookmarklet() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "bookmarklet",
+ url: "javascript:'1';" });
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction (function* () {
+ gBrowser.removeTab(tab);
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+ yield promisePageShow();
+ let originalPrincipal = gBrowser.contentPrincipal;
+
+ function getPrincipalURI() {
+ return ContentTask.spawn(tab.linkedBrowser, null, function() {
+ return content.document.nodePrincipal.URI.spec;
+ });
+ }
+
+ let originalPrincipalURI = yield getPrincipalURI();
+
+ yield PlacesUtils.keywords.insert({ keyword: "bm", url: "javascript:'1';" })
+
+ // Enter bookmarklet keyword in the URL bar
+ gURLBar.value = "bm";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ yield promisePageShow();
+
+ let newPrincipalURI = yield getPrincipalURI();
+ is(newPrincipalURI, originalPrincipalURI, "content has the same principal");
+
+ // In e10s, null principals don't round-trip so the same null principal sent
+ // from the child will be a new null principal. Verify that this is the
+ // case.
+ if (tab.linkedBrowser.isRemoteBrowser) {
+ ok(originalPrincipal.isNullPrincipal && gBrowser.contentPrincipal.isNullPrincipal,
+ "both principals should be null principals in the parent");
+ } else {
+ ok(gBrowser.contentPrincipal.equals(originalPrincipal),
+ "javascript bookmarklet should inherit principal");
+ }
+});
+
+function* promisePageShow() {
+ return new Promise(resolve => {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function listen() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", listen);
+ resolve();
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_keywordSearch.js b/browser/base/content/test/general/browser_keywordSearch.js
new file mode 100644
index 000000000..cf8bd0c0e
--- /dev/null
+++ b/browser/base/content/test/general/browser_keywordSearch.js
@@ -0,0 +1,88 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ **/
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ testText: "test search",
+ searchURL: Services.search.defaultEngine.getSubmission("test search", null, "keyword").uri.spec
+ },
+ {
+ name: "?-prefixed search (search service)",
+ testText: "? foo ",
+ searchURL: Services.search.defaultEngine.getSubmission("foo", null, "keyword").uri.spec
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let windowObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ ok(false, "Alert window opened");
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+ win.addEventListener("load", function() {
+ win.removeEventListener("load", arguments.callee, false);
+ win.close();
+ }, false);
+ executeSoon(finish);
+ }
+ }
+ };
+
+ Services.ww.registerNotification(windowObserver);
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onLocationChange(webProgress, req, flags, status) {
+ // Only care about document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart))
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ };
+ gBrowser.addProgressListener(listener);
+
+ registerCleanupFunction(function () {
+ Services.ww.unregisterNotification(windowObserver);
+
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ });
+
+ nextTest();
+}
+
+var gCurrTest;
+function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ doTest();
+ } else {
+ finish();
+ }
+}
+
+function doTest() {
+ info("Running test: " + gCurrTest.name);
+
+ // Simulate a user entering search terms
+ gURLBar.value = gCurrTest.testText;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
diff --git a/browser/base/content/test/general/browser_keywordSearch_postData.js b/browser/base/content/test/general/browser_keywordSearch_postData.js
new file mode 100644
index 000000000..3f700fa58
--- /dev/null
+++ b/browser/base/content/test/general/browser_keywordSearch_postData.js
@@ -0,0 +1,94 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ **/
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ testText: "test search",
+ expectText: "test+search"
+ },
+ {
+ name: "?-prefixed search (search service)",
+ testText: "? foo ",
+ expectText: "foo"
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let searchObserver = function search_observer(aSubject, aTopic, aData) {
+ let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + aData + " for " + engine.name);
+
+ if (aData != "engine-added")
+ return;
+
+ if (engine.name != "POST Search")
+ return;
+
+ Services.search.defaultEngine = engine;
+
+ registerCleanupFunction(function () {
+ Services.search.removeEngine(engine);
+ });
+
+ // ready to execute the tests!
+ executeSoon(nextTest);
+ };
+
+ Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+
+ Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+ });
+
+ Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
+ null, null, false);
+}
+
+var gCurrTest;
+function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ doTest();
+ } else {
+ finish();
+ }
+}
+
+function doTest() {
+ info("Running test: " + gCurrTest.name);
+
+ waitForLoad(function () {
+ let loadedText = gBrowser.contentDocument.body.textContent;
+ ok(loadedText, "search page loaded");
+ let needle = "searchterms=" + gCurrTest.expectText;
+ is(loadedText, needle, "The query POST data should be returned in the response");
+ nextTest();
+ });
+
+ // Simulate a user entering search terms
+ gURLBar.value = gCurrTest.testText;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
+
+
+function waitForLoad(cb) {
+ let browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function listener() {
+ if (browser.currentURI.spec == "about:blank")
+ return;
+ info("Page loaded: " + browser.currentURI.spec);
+ browser.removeEventListener("load", listener, true);
+
+ cb();
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_lastAccessedTab.js b/browser/base/content/test/general/browser_lastAccessedTab.js
new file mode 100644
index 000000000..57bd330ae
--- /dev/null
+++ b/browser/base/content/test/general/browser_lastAccessedTab.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// gBrowser.selectedTab.lastAccessed and Date.now() called from this test can't
+// run concurrently, and therefore don't always match exactly.
+const CURRENT_TIME_TOLERANCE_MS = 15;
+
+function isCurrent(tab, msg) {
+ const DIFF = Math.abs(Date.now() - tab.lastAccessed);
+ ok(DIFF <= CURRENT_TIME_TOLERANCE_MS, msg + " (difference: " + DIFF + ")");
+}
+
+function nextStep(fn) {
+ setTimeout(fn, CURRENT_TIME_TOLERANCE_MS + 10);
+}
+
+var originalTab;
+var newTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ originalTab = gBrowser.selectedTab;
+ nextStep(step2);
+}
+
+function step2() {
+ isCurrent(originalTab, "selected tab has the current timestamp");
+ newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ nextStep(step3);
+}
+
+function step3() {
+ ok(newTab.lastAccessed < Date.now(), "new tab hasn't been selected so far");
+ gBrowser.selectedTab = newTab;
+ isCurrent(newTab, "new tab has the current timestamp after being selected");
+ nextStep(step4);
+}
+
+function step4() {
+ ok(originalTab.lastAccessed < Date.now(),
+ "original tab has old timestamp after being deselected");
+ isCurrent(newTab, "new tab has the current timestamp since it's still selected");
+
+ gBrowser.removeTab(newTab);
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_mcb_redirect.js b/browser/base/content/test/general/browser_mcb_redirect.js
new file mode 100644
index 000000000..41b4e9468
--- /dev/null
+++ b/browser/base/content/test/general/browser_mcb_redirect.js
@@ -0,0 +1,314 @@
+/*
+ * Description of the Tests for
+ * - Bug 418354 - Call Mixed content blocking on redirects
+ *
+ * Single redirect script tests
+ * 1. Load a script over https inside an https page
+ * - the server responds with a 302 redirect to a >> HTTP << script
+ * - the doorhanger should appear!
+ *
+ * 2. Load a script over https inside an http page
+ * - the server responds with a 302 redirect to a >> HTTP << script
+ * - the doorhanger should not appear!
+ *
+ * Single redirect image tests
+ * 3. Load an image over https inside an https page
+ * - the server responds with a 302 redirect to a >> HTTP << image
+ * - the image should not load
+ *
+ * 4. Load an image over https inside an http page
+ * - the server responds with a 302 redirect to a >> HTTP << image
+ * - the image should load and get cached
+ *
+ * Single redirect cached image tests
+ * 5. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an http page
+ * - the server would have responded with a 302 redirect to a >> HTTP <<
+ * image, but instead we try to use the cached image.
+ * - the image should load
+ *
+ * 6. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an https page
+ * - the server would have responded with a 302 redirect to a >> HTTP <<
+ * image, but instead we try to use the cached image.
+ * - the image should not load
+ *
+ * Double redirect image test
+ * 7. Load an image over https inside an http page
+ * - the server responds with a 302 redirect to a >> HTTP << server
+ * - the HTTP server responds with a 302 redirect to a >> HTTPS << image
+ * - the image should load and get cached
+ *
+ * Double redirect cached image tests
+ * 8. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an http page
+ * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS,
+ * but instead we try to use the cached image.
+ * - the image should load
+ *
+ * 9. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an https page
+ * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS,
+ * but instead we try to use the cached image.
+ * - the image should not load
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const gHttpsTestRoot = "https://example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/";
+
+var origBlockActive;
+var origBlockDisplay;
+var gTestBrowser = null;
+
+// ------------------------ Helper Functions ---------------------
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+ Services.prefs.setBoolPref(PREF_DISPLAY, origBlockDisplay);
+
+ // Make sure we are online again
+ Services.io.offline = false;
+});
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function waitForCondition(condition, nextTest, errorMsg, okMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ ok(true, okMsg)
+ moveOn();
+ }
+ tries++;
+ }, 500);
+ var moveOn = function() {
+ clearInterval(interval); nextTest();
+ };
+}
+
+// ------------------------ Test 1 ------------------------------
+
+function test1() {
+ gTestBrowser.addEventListener("load", checkUIForTest1, true);
+ var url = gHttpsTestRoot + "test_mcb_redirect.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkUIForTest1() {
+ gTestBrowser.removeEventListener("load", checkUIForTest1, true);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ var expected = "script blocked";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test2, "Error: Waited too long for status in Test 1!",
+ "OK: Expected result in innerHTML for Test1!");
+}
+
+// ------------------------ Test 2 ------------------------------
+
+function test2() {
+ gTestBrowser.addEventListener("load", checkUIForTest2, true);
+ var url = gHttpTestRoot + "test_mcb_redirect.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkUIForTest2() {
+ gTestBrowser.removeEventListener("load", checkUIForTest2, true);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false});
+
+ var expected = "script executed";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test3, "Error: Waited too long for status in Test 2!",
+ "OK: Expected result in innerHTML for Test2!");
+}
+
+// ------------------------ Test 3 ------------------------------
+// HTTPS page loading insecure image
+function test3() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest3, true);
+ var url = gHttpsTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest3() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest3, true);
+
+ var expected = "image blocked"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test4, "Error: Waited too long for status in Test 3!",
+ "OK: Expected result in innerHTML for Test3!");
+}
+
+// ------------------------ Test 4 ------------------------------
+// HTTP page loading insecure image
+function test4() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest4, true);
+ var url = gHttpTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest4() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest4, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test5, "Error: Waited too long for status in Test 4!",
+ "OK: Expected result in innerHTML for Test4!");
+}
+
+// ------------------------ Test 5 ------------------------------
+// HTTP page laoding insecure cached image
+// Assuming test 4 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test5() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest5, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest5() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest5, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test6, "Error: Waited too long for status in Test 5!",
+ "OK: Expected result in innerHTML for Test5!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ Test 6 ------------------------------
+// HTTPS page loading insecure cached image
+// Assuming test 4 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test6() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest6, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpsTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest6() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest6, true);
+
+ var expected = "image blocked"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test7, "Error: Waited too long for status in Test 6!",
+ "OK: Expected result in innerHTML for Test6!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ Test 7 ------------------------------
+// HTTP page loading insecure image that went through a double redirect
+function test7() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest7, true);
+ var url = gHttpTestRoot + "test_mcb_double_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest7() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest7, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test8, "Error: Waited too long for status in Test 7!",
+ "OK: Expected result in innerHTML for Test7!");
+}
+
+// ------------------------ Test 8 ------------------------------
+// HTTP page loading insecure cached image that went through a double redirect
+// Assuming test 7 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test8() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest8, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpTestRoot + "test_mcb_double_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest8() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest8, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test9, "Error: Waited too long for status in Test 8!",
+ "OK: Expected result in innerHTML for Test8!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ Test 9 ------------------------------
+// HTTPS page loading insecure cached image that went through a double redirect
+// Assuming test 7 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test9() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest9, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpsTestRoot + "test_mcb_double_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest9() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest9, true);
+
+ var expected = "image blocked"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ cleanUpAfterTests, "Error: Waited too long for status in Test 9!",
+ "OK: Expected result in innerHTML for Test9!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ SETUP ------------------------------
+
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+ origBlockDisplay = Services.prefs.getBoolPref(PREF_DISPLAY);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+ Services.prefs.setBoolPref(PREF_DISPLAY, true);
+
+ pushPrefs(["dom.ipc.processCount", 1]).then(() => {
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop();
+
+ executeSoon(test1);
+ });
+}
diff --git a/browser/base/content/test/general/browser_menuButtonBadgeManager.js b/browser/base/content/test/general/browser_menuButtonBadgeManager.js
new file mode 100644
index 000000000..9afe39ab7
--- /dev/null
+++ b/browser/base/content/test/general/browser_menuButtonBadgeManager.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var menuButton = document.getElementById("PanelUI-menu-button");
+
+add_task(function* testButtonActivities() {
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+ is(menuButton.hasAttribute("badge"), false, "Should not have the badge attribute set");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
+ is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
+ is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-failed");
+ is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-severe");
+ is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-warning");
+ is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status");
+
+ gMenuButtonBadgeManager.addBadge("unknownbadge", "attr");
+ is(menuButton.getAttribute("badge-status"), "download-warning", "Should not have changed badge status");
+
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+ is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
+
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE);
+ is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
+
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+
+ yield PanelUI.show();
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
+ PanelUI.hide();
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
+ gMenuButtonBadgeManager.clearBadges();
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (clearBadges called)");
+});
diff --git a/browser/base/content/test/general/browser_menuButtonFitts.js b/browser/base/content/test/general/browser_menuButtonFitts.js
new file mode 100644
index 000000000..e2541b925
--- /dev/null
+++ b/browser/base/content/test/general/browser_menuButtonFitts.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function test () {
+ waitForExplicitFinish();
+ window.maximize();
+
+ // Find where the nav-bar is vertically.
+ var navBar = document.getElementById("nav-bar");
+ var boundingRect = navBar.getBoundingClientRect();
+ var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
+ var xPixel = boundingRect.width - 1; // Use the last pixel of the screen since it is maximized.
+
+ function onPopupHidden() {
+ PanelUI.panel.removeEventListener("popuphidden", onPopupHidden);
+ window.restore();
+ finish();
+ }
+ function onPopupShown() {
+ PanelUI.panel.removeEventListener("popupshown", onPopupShown);
+ ok(true, "Clicking at the far edge of the window opened the menu popup.");
+ PanelUI.panel.addEventListener("popuphidden", onPopupHidden);
+ PanelUI.hide();
+ }
+ registerCleanupFunction(function() {
+ PanelUI.panel.removeEventListener("popupshown", onPopupShown);
+ PanelUI.panel.removeEventListener("popuphidden", onPopupHidden);
+ });
+ PanelUI.panel.addEventListener("popupshown", onPopupShown);
+ EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
+}
diff --git a/browser/base/content/test/general/browser_middleMouse_noJSPaste.js b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
new file mode 100644
index 000000000..fa0c26f78
--- /dev/null
+++ b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const middleMousePastePref = "middlemouse.contentLoadURL";
+const autoScrollPref = "general.autoScroll";
+
+add_task(function* () {
+ yield pushPrefs([middleMousePastePref, true], [autoScrollPref, false]);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let url = "javascript:http://www.example.com/";
+ yield new Promise((resolve, reject) => {
+ SimpleTest.waitForClipboard(url, () => {
+ Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Components.interfaces.nsIClipboardHelper)
+ .copyString(url);
+ }, resolve, () => {
+ ok(false, "Clipboard copy failed");
+ reject();
+ });
+ });
+
+ let middlePagePromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Middle click on the content area
+ info("Middle clicking");
+ yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {button: 1}, gBrowser.selectedBrowser);
+ yield middlePagePromise;
+
+ is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_minimize.js b/browser/base/content/test/general/browser_minimize.js
new file mode 100644
index 000000000..1d761c0da
--- /dev/null
+++ b/browser/base/content/test/general/browser_minimize.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function *() {
+ registerCleanupFunction(function() {
+ window.restore();
+ });
+ function waitForActive() { return gBrowser.selectedTab.linkedBrowser.docShellIsActive; }
+ function waitForInactive() { return !gBrowser.selectedTab.linkedBrowser.docShellIsActive; }
+ yield promiseWaitForCondition(waitForActive);
+ is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, true, "Docshell should be active");
+ window.minimize();
+ yield promiseWaitForCondition(waitForInactive);
+ is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, false, "Docshell should be Inactive");
+ window.restore();
+ yield promiseWaitForCondition(waitForActive);
+ is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, true, "Docshell should be active again");
+});
diff --git a/browser/base/content/test/general/browser_misused_characters_in_strings.js b/browser/base/content/test/general/browser_misused_characters_in_strings.js
new file mode 100644
index 000000000..fe8022662
--- /dev/null
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -0,0 +1,244 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' issues to remain, while we
+ * detect newly occurring issues in shipping files. It is a list of objects
+ * specifying conditions under which an error should be ignored.
+ *
+ * As each issue is found in the whitelist, it is removed from the list. At
+ * the end of the test, there is an assertion that all items have been
+ * removed from the whitelist, thus ensuring there are no stale entries. */
+let gWhitelist = [{
+ file: "search.properties",
+ key: "searchForSomethingWith",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "certerror.introPara",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "weakCryptoAdvanced.longDesc",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "weakCryptoAdvanced.override",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "inadequateSecurityError.longDesc",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "certerror.wrongSystemTime",
+ type: "single-quote"
+ }, {
+ file: "phishing-afterload-warning-message.dtd",
+ key: "safeb.blocked.malwarePage.shortDesc",
+ type: "single-quote"
+ }, {
+ file: "phishing-afterload-warning-message.dtd",
+ key: "safeb.blocked.unwantedPage.shortDesc",
+ type: "single-quote"
+ }, {
+ file: "phishing-afterload-warning-message.dtd",
+ key: "safeb.blocked.phishingPage.shortDesc2",
+ type: "single-quote"
+ }, {
+ file: "mathfont.properties",
+ key: "operator.\\u002E\\u002E\\u002E.postfix",
+ type: "ellipsis"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapRectBoundsError",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapCircleWrongNumberOfCoords",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapCircleNegativeRadius",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapPolyWrongNumberOfCoords",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapPolyOddNumberOfCoords",
+ type: "double-quote"
+ }, {
+ file: "xbl.properties",
+ key: "CommandNotInChrome",
+ type: "double-quote"
+ }, {
+ file: "dom.properties",
+ key: "PatternAttributeCompileFailure",
+ type: "single-quote"
+ }, {
+ file: "pipnss.properties",
+ key: "certErrorMismatchSingle2",
+ type: "double-quote"
+ }, {
+ file: "pipnss.properties",
+ key: "certErrorCodePrefix2",
+ type: "double-quote"
+ }, {
+ file: "aboutSupport.dtd",
+ key: "aboutSupport.pageSubtitle",
+ type: "single-quote"
+ }, {
+ file: "aboutSupport.dtd",
+ key: "aboutSupport.userJSDescription",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "inadequateSecurityError.longDesc",
+ type: "single-quote"
+ }, {
+ file: "netErrorApp.dtd",
+ key: "securityOverride.warningContent",
+ type: "single-quote"
+ }, {
+ file: "pocket.properties",
+ key: "tos",
+ type: "double-quote"
+ }, {
+ file: "pocket.properties",
+ key: "tos",
+ type: "apostrophe"
+ }, {
+ file: "aboutNetworking.dtd",
+ key: "aboutNetworking.logTutorial",
+ type: "single-quote"
+ }
+];
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in gWhitelist.
+ *
+ * @param filepath The URI spec of the locale file
+ * @param key The key of the entity that is being checked
+ * @param type The type of error that has been found
+ * @return true if the error should be ignored, false otherwise.
+ */
+function ignoredError(filepath, key, type) {
+ for (let index in gWhitelist) {
+ let whitelistItem = gWhitelist[index];
+ if (filepath.endsWith(whitelistItem.file) &&
+ key == whitelistItem.key &&
+ type == whitelistItem.type) {
+ gWhitelist.splice(index, 1);
+ return true;
+ }
+ }
+ return false;
+}
+
+function fetchFile(uri) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (this.readyState != this.DONE) {
+ return;
+ }
+ try {
+ resolve(this.responseText);
+ } catch (ex) {
+ ok(false, `Script error reading ${uri}: ${ex}`);
+ resolve("");
+ }
+ };
+ xhr.onerror = error => {
+ ok(false, `XHR error reading ${uri}: ${error}`);
+ resolve("");
+ };
+ xhr.send(null);
+ });
+}
+
+function testForError(filepath, key, str, pattern, type, helpText) {
+ if (str.match(pattern) &&
+ !ignoredError(filepath, key, type)) {
+ ok(false, `${filepath} with key=${key} has a misused ${type}. ${helpText}`);
+ }
+}
+
+function testForErrors(filepath, key, str) {
+ testForError(filepath, key, str, /\w'\w/, "apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo's.");
+ testForError(filepath, key, str, /\w\u2018\w/, "incorrect-apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo\u2018s.");
+ testForError(filepath, key, str, /'.+'/, "single-quote", "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'.");
+ testForError(filepath, key, str, /"/, "double-quote", "Double-quoted strings should use Unicode \u201cfoo\u201d instead of \"foo\".");
+ testForError(filepath, key, str, /\.\.\./, "ellipsis", "Strings with an ellipsis should use the Unicode \u2026 character instead of three periods.");
+}
+
+function* getAllTheFiles(extension) {
+ let appDirGreD = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let appDirXCurProcD = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+ if (appDirGreD.contains(appDirXCurProcD)) {
+ return yield generateURIsFromDirTree(appDirGreD, [extension]);
+ }
+ if (appDirXCurProcD.contains(appDirGreD)) {
+ return yield generateURIsFromDirTree(appDirXCurProcD, [extension]);
+ }
+ let urisGreD = yield generateURIsFromDirTree(appDirGreD, [extension]);
+ let urisXCurProcD = yield generateURIsFromDirTree(appDirXCurProcD, [extension]);
+ return Array.from(new Set(urisGreD.concat(appDirXCurProcD)));
+}
+
+add_task(function* checkAllTheProperties() {
+ // This asynchronously produces a list of URLs (sadly, mostly sync on our
+ // test infrastructure because it runs against jarfiles there, and
+ // our zipreader APIs are all sync)
+ let uris = yield getAllTheFiles(".properties");
+ ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`);
+
+ for (let uri of uris) {
+ let bundle = new StringBundle(uri.spec);
+ let entities = bundle.getAll();
+ for (let entity of entities) {
+ testForErrors(uri.spec, entity.key, entity.value);
+ }
+ }
+});
+
+var checkDTD = Task.async(function* (aURISpec) {
+ let rawContents = yield fetchFile(aURISpec);
+ // The regular expression below is adapted from:
+ // https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233
+ let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g);
+ if (!entities) {
+ // Some files, such as requestAutocomplete.dtd, have no entities defined.
+ return;
+ }
+ for (let entity of entities) {
+ let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/);
+ // The matched string includes the enclosing quotation marks,
+ // we need to slice them off.
+ str = str.slice(1, -1);
+ testForErrors(aURISpec, key, str);
+ }
+});
+
+add_task(function* checkAllTheDTDs() {
+ let uris = yield getAllTheFiles(".dtd");
+ ok(uris.length, `Found ${uris.length} .dtd files to scan for misused characters`);
+ for (let uri of uris) {
+ yield checkDTD(uri.spec);
+ }
+
+ // This support DTD file supplies a string with a newline to make sure
+ // the regex in checkDTD works correctly for that case.
+ let dtdLocation = gTestPath.replace(/\/[^\/]*$/i, "/bug1262648_string_with_newlines.dtd");
+ yield checkDTD(dtdLocation);
+});
+
+add_task(function* ensureWhiteListIsEmpty() {
+ is(gWhitelist.length, 0, "No remaining whitelist entries exist");
+});
diff --git a/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js
new file mode 100644
index 000000000..ac19efd05
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js
@@ -0,0 +1,34 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Test for Bug 1182551 -
+ *
+ * This test has a top level HTTP page with an HTTPS iframe. The HTTPS iframe
+ * includes an HTTP image. We check that the top level security state is
+ * STATE_IS_INSECURE. The mixed content from the iframe shouldn't "upgrade"
+ * the HTTP top level page to broken HTTPS.
+ */
+
+const gHttpTestUrl = "http://example.com/browser/browser/base/content/test/general/file_mixedContentFramesOnHttp.html";
+
+var gTestBrowser = null;
+
+add_task(function *() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["security.mixed_content.block_active_content", true],
+ ["security.mixed_content.block_display_content", false]
+ ]
+ }, resolve);
+ });
+ let url = gHttpTestUrl
+ yield BrowserTestUtils.withNewTab({gBrowser, url}, function*() {
+ gTestBrowser = gBrowser.selectedBrowser;
+ // check security state is insecure
+ isSecurityState("insecure");
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: true});
+ });
+});
+
diff --git a/browser/base/content/test/general/browser_mixedContentFromOnunload.js b/browser/base/content/test/general/browser_mixedContentFromOnunload.js
new file mode 100644
index 000000000..9b39776f4
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedContentFromOnunload.js
@@ -0,0 +1,49 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Tests for Bug 947079 - Fix bug in nsSecureBrowserUIImpl that sets the wrong
+ * security state on a page because of a subresource load that is not on the
+ * same page.
+ */
+
+// We use different domains for each test and for navigation within each test
+const gHttpTestRoot1 = "http://example.com/browser/browser/base/content/test/general/";
+const gHttpsTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "http://example.net/browser/browser/base/content/test/general/";
+const gHttpsTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/";
+
+var gTestBrowser = null;
+add_task(function *() {
+ let url = gHttpTestRoot1 + "file_mixedContentFromOnunload.html";
+ yield BrowserTestUtils.withNewTab({gBrowser, url}, function*() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["security.mixed_content.block_active_content", true],
+ ["security.mixed_content.block_display_content", false]
+ ]
+ }, resolve);
+ });
+ gTestBrowser = gBrowser.selectedBrowser;
+ // Navigation from an http page to a https page with no mixed content
+ // The http page loads an http image on unload
+ url = gHttpsTestRoot1 + "file_mixedContentFromOnunload_test1.html";
+ yield BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+ // check security state. Since current url is https and doesn't have any
+ // mixed content resources, we expect it to be secure.
+ isSecurityState("secure");
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false});
+ // Navigation from an http page to a https page that has mixed display content
+ // The https page loads an http image on unload
+ url = gHttpTestRoot2 + "file_mixedContentFromOnunload.html";
+ yield BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+ url = gHttpsTestRoot2 + "file_mixedContentFromOnunload_test2.html";
+ yield BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+ isSecurityState("broken");
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: true});
+ });
+});
diff --git a/browser/base/content/test/general/browser_mixed_content_cert_override.js b/browser/base/content/test/general/browser_mixed_content_cert_override.js
new file mode 100644
index 000000000..037fce5d2
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixed_content_cert_override.js
@@ -0,0 +1,54 @@
+/*
+ * Bug 1253771 - check mixed content blocking in combination with overriden certificates
+ */
+
+"use strict";
+
+const MIXED_CONTENT_URL = "https://self-signed.example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html";
+
+function getConnectionState() {
+ return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+function getPopupContentVerifier() {
+ return document.getElementById("identity-popup-content-verifier");
+}
+
+function getConnectionIcon() {
+ return window.getComputedStyle(document.getElementById("connection-icon")).listStyleImage;
+}
+
+function checkIdentityPopup(icon) {
+ gIdentityHandler.refreshIdentityPopup();
+ is(getConnectionIcon(), `url("chrome://browser/skin/${icon}")`);
+ is(getConnectionState(), "secure-cert-user-overridden");
+ isnot(getPopupContentVerifier().style.display, "none", "Overridden certificate warning is shown");
+ ok(getPopupContentVerifier().textContent.includes("security exception"), "Text shows overridden certificate warning.");
+}
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // check that a warning is shown when loading a page with mixed content and an overridden certificate
+ yield loadBadCertPage(MIXED_CONTENT_URL);
+ checkIdentityPopup("connection-mixed-passive-loaded.svg#icon");
+
+ // check that the crossed out icon is shown when disabling mixed content protection
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ checkIdentityPopup("connection-mixed-active-loaded.svg#icon");
+
+ // check that a warning is shown even without mixed content
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://self-signed.example.com");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ checkIdentityPopup("connection-mixed-passive-loaded.svg#icon");
+
+ // remove cert exception
+ let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("self-signed.example.com", -1);
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
diff --git a/browser/base/content/test/general/browser_mixedcontent_securityflags.js b/browser/base/content/test/general/browser_mixedcontent_securityflags.js
new file mode 100644
index 000000000..1c2614b86
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedcontent_securityflags.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a web page with mixed active and mixed display content and
+// makes sure that the mixed content flags on the docshell are set correctly.
+// * Using default about:config prefs (mixed active blocked, mixed display
+// loaded) we load the page and check the flags.
+// * We change the about:config prefs (mixed active blocked, mixed display
+// blocked), reload the page, and check the flags again.
+// * We override protection so all mixed content can load and check the
+// flags again.
+
+const TEST_URI = "https://example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html";
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.clearUserPref(PREF_DISPLAY);
+ Services.prefs.clearUserPref(PREF_ACTIVE);
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* blockMixedActiveContentTest() {
+ // Turn on mixed active blocking and mixed display loading and load the page.
+ Services.prefs.setBoolPref(PREF_DISPLAY, false);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+ gTestBrowser = gBrowser.getBrowserForTab(tab);
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ is(docShell.hasMixedDisplayContentBlocked, false, "hasMixedDisplayContentBlocked flag has been set");
+ is(docShell.hasMixedActiveContentBlocked, true, "hasMixedActiveContentBlocked flag has been set");
+ is(docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has been set");
+ is(docShell.hasMixedActiveContentLoaded, false, "hasMixedActiveContentLoaded flag has been set");
+ });
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: true});
+
+ // Turn on mixed active and mixed display blocking and reload the page.
+ Services.prefs.setBoolPref(PREF_DISPLAY, true);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ gBrowser.reload();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ is(docShell.hasMixedDisplayContentBlocked, true, "hasMixedDisplayContentBlocked flag has been set");
+ is(docShell.hasMixedActiveContentBlocked, true, "hasMixedActiveContentBlocked flag has been set");
+ is(docShell.hasMixedDisplayContentLoaded, false, "hasMixedDisplayContentLoaded flag has been set");
+ is(docShell.hasMixedActiveContentLoaded, false, "hasMixedActiveContentLoaded flag has been set");
+ });
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+});
+
+add_task(function* overrideMCB() {
+ // Disable mixed content blocking (reloads page) and retest
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ is(docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has not been set");
+ is(docShell.hasMixedActiveContentLoaded, true, "hasMixedActiveContentLoaded flag has not been set");
+ is(docShell.hasMixedDisplayContentBlocked, false, "second hasMixedDisplayContentBlocked flag has been set");
+ is(docShell.hasMixedActiveContentBlocked, false, "second hasMixedActiveContentBlocked flag has been set");
+ });
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: true});
+});
diff --git a/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js
new file mode 100644
index 000000000..3b5a5a149
--- /dev/null
+++ b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const kURL =
+ "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+ "data:text/html,<a href=''>Middle-click me</a>";
+
+/*
+ * Check that when manually opening content JS links in new tabs/windows,
+ * we use the correct principal, and we don't clear the URL bar.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab(kURL, function* (browser) {
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ yield ContentTask.spawn(browser, null, function* () {
+ let a = content.document.createElement("a");
+ a.href = "javascript:document.write('spoof'); void(0);";
+ a.textContent = "Some link";
+ content.document.body.appendChild(a);
+ });
+ info("Added element");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a", {button: 1}, browser);
+ let newTab = yield newTabPromise;
+ is(newTab.linkedBrowser.contentPrincipal.origin, "http://example.com",
+ "Principal should be for example.com");
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ info(gURLBar.value);
+ isnot(gURLBar.value, "", "URL bar should not be empty.");
+ yield BrowserTestUtils.removeTab(newTab);
+ });
+});
diff --git a/browser/base/content/test/general/browser_newTabDrop.js b/browser/base/content/test/general/browser_newTabDrop.js
new file mode 100644
index 000000000..03c90df3f
--- /dev/null
+++ b/browser/base/content/test/general/browser_newTabDrop.js
@@ -0,0 +1,99 @@
+registerCleanupFunction(function* cleanup() {
+ while (gBrowser.tabs.length > 1) {
+ yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+ }
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+});
+
+let originalEngine;
+add_task(function* test_setup() {
+ // Stop search-engine loads from hitting the network
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+});
+
+// New Tab Button opens any link.
+add_task(function*() { yield dropText("mochi.test/first", 1); });
+add_task(function*() { yield dropText("javascript:'bad'", 1); });
+add_task(function*() { yield dropText("jAvascript:'bad'", 1); });
+add_task(function*() { yield dropText("mochi.test/second", 1); });
+add_task(function*() { yield dropText("data:text/html,bad", 1); });
+add_task(function*() { yield dropText("mochi.test/third", 1); });
+
+// Single text/plain item, with multiple links.
+add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); });
+add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 2); });
+add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 2); });
+
+// Multiple text/plain items, with single and multiple links.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/5"}],
+ [{type: "text/plain",
+ data: "mochi.test/6\nmochi.test/7"}]], 3);
+});
+
+// Single text/x-moz-url item, with multiple links.
+// "text/x-moz-url" has titles in even-numbered lines.
+add_task(function*() {
+ yield drop([[{type: "text/x-moz-url",
+ data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2);
+});
+
+// Single item with multiple types.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/10"},
+ {type: "text/x-moz-url",
+ data: "mochi.test/11\nTITLE11"}]], 1);
+});
+
+function dropText(text, expectedTabOpenCount=0) {
+ return drop([[{type: "text/plain", data: text}]], expectedTabOpenCount);
+}
+
+function* drop(dragData, expectedTabOpenCount=0) {
+ let dragDataString = JSON.stringify(dragData);
+ info(`Starting test for datagData:${dragDataString}; expectedTabOpenCount:${expectedTabOpenCount}`);
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // Since synthesizeDrop triggers the srcElement, need to use another button.
+ let dragSrcElement = document.getElementById("downloads-button");
+ ok(dragSrcElement, "Downloads button exists");
+ let newTabButton = document.getElementById("new-tab-button");
+ ok(newTabButton, "New Tab button exists");
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(newTabButton, "drop");
+ let actualTabOpenCount = 0;
+ let openedTabs = [];
+ let checkCount = function(event) {
+ openedTabs.push(event.target);
+ actualTabOpenCount++;
+ return actualTabOpenCount == expectedTabOpenCount;
+ };
+ let awaitTabOpen = expectedTabOpenCount && BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", false, checkCount);
+
+ EventUtils.synthesizeDrop(dragSrcElement, newTabButton, dragData, "link", window);
+
+ let tabsOpened = false;
+ if (awaitTabOpen) {
+ yield awaitTabOpen;
+ info("Got TabOpen event");
+ tabsOpened = true;
+ for (let tab of openedTabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ }
+ is(tabsOpened, !!expectedTabOpenCount, `Tabs for ${dragDataString} should only open if any of dropped items are valid`);
+
+ yield awaitDrop;
+ ok(true, "Got drop event");
+}
diff --git a/browser/base/content/test/general/browser_newWindowDrop.js b/browser/base/content/test/general/browser_newWindowDrop.js
new file mode 100644
index 000000000..f404d4eed
--- /dev/null
+++ b/browser/base/content/test/general/browser_newWindowDrop.js
@@ -0,0 +1,120 @@
+registerCleanupFunction(function* cleanup() {
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+});
+
+let originalEngine;
+add_task(function* test_setup() {
+ // Opening multiple windows on debug build takes too long time.
+ requestLongerTimeout(10);
+
+ // Stop search-engine loads from hitting the network
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ // Move New Window button to nav bar, to make it possible to drag and drop.
+ let {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+ let origPlacement = CustomizableUI.getPlacementOfWidget("new-window-button");
+ if (!origPlacement || origPlacement.area != CustomizableUI.AREA_NAVBAR) {
+ CustomizableUI.addWidgetToArea("new-window-button",
+ CustomizableUI.AREA_NAVBAR,
+ 0);
+ CustomizableUI.ensureWidgetPlacedInWindow("new-window-button", window);
+ registerCleanupFunction(function () {
+ CustomizableUI.reset();
+ });
+ }
+});
+
+// New Window Button opens any link.
+add_task(function*() { yield dropText("mochi.test/first", 1); });
+add_task(function*() { yield dropText("javascript:'bad'", 1); });
+add_task(function*() { yield dropText("jAvascript:'bad'", 1); });
+add_task(function*() { yield dropText("mochi.test/second", 1); });
+add_task(function*() { yield dropText("data:text/html,bad", 1); });
+add_task(function*() { yield dropText("mochi.test/third", 1); });
+
+// Single text/plain item, with multiple links.
+add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); });
+add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 2); });
+add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 2); });
+
+// Multiple text/plain items, with single and multiple links.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/5"}],
+ [{type: "text/plain",
+ data: "mochi.test/6\nmochi.test/7"}]], 3);
+});
+
+// Single text/x-moz-url item, with multiple links.
+// "text/x-moz-url" has titles in even-numbered lines.
+add_task(function*() {
+ yield drop([[{type: "text/x-moz-url",
+ data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2);
+});
+
+// Single item with multiple types.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/10"},
+ {type: "text/x-moz-url",
+ data: "mochi.test/11\nTITLE11"}]], 1);
+});
+
+function dropText(text, expectedWindowOpenCount=0) {
+ return drop([[{type: "text/plain", data: text}]], expectedWindowOpenCount);
+}
+
+function* drop(dragData, expectedWindowOpenCount=0) {
+ let dragDataString = JSON.stringify(dragData);
+ info(`Starting test for datagData:${dragDataString}; expectedWindowOpenCount:${expectedWindowOpenCount}`);
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // Since synthesizeDrop triggers the srcElement, need to use another button.
+ let dragSrcElement = document.getElementById("downloads-button");
+ ok(dragSrcElement, "Downloads button exists");
+ let newWindowButton = document.getElementById("new-window-button");
+ ok(newWindowButton, "New Window button exists");
+
+ let tmp = {};
+ Cu.import("resource://testing-common/TestUtils.jsm", tmp);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(newWindowButton, "drop");
+ let actualWindowOpenCount = 0;
+ let openedWindows = [];
+ let checkCount = function(window) {
+ // Add observer as soon as domWindow is opened to avoid missing the topic.
+ let awaitStartup = tmp.TestUtils.topicObserved("browser-delayed-startup-finished",
+ subject => subject == window);
+ openedWindows.push([window, awaitStartup]);
+ actualWindowOpenCount++;
+ return actualWindowOpenCount == expectedWindowOpenCount;
+ };
+ let awaitWindowOpen = expectedWindowOpenCount && BrowserTestUtils.domWindowOpened(null, checkCount);
+
+ EventUtils.synthesizeDrop(dragSrcElement, newWindowButton, dragData, "link", window);
+
+ let windowsOpened = false;
+ if (awaitWindowOpen) {
+ yield awaitWindowOpen;
+ info("Got Window opened");
+ windowsOpened = true;
+ for (let [window, awaitStartup] of openedWindows.reverse()) {
+ // Wait for startup before closing, to properly close the browser window.
+ yield awaitStartup;
+ yield BrowserTestUtils.closeWindow(window);
+ }
+ }
+ is(windowsOpened, !!expectedWindowOpenCount, `Windows for ${dragDataString} should only open if any of dropped items are valid`);
+
+ yield awaitDrop;
+ ok(true, "Got drop event");
+}
diff --git a/browser/base/content/test/general/browser_newwindow_focus.js b/browser/base/content/test/general/browser_newwindow_focus.js
new file mode 100644
index 000000000..7880db0bd
--- /dev/null
+++ b/browser/base/content/test/general/browser_newwindow_focus.js
@@ -0,0 +1,96 @@
+"use strict";
+
+/**
+ * These tests are for the auto-focus behaviour on the initial browser
+ * when a window is opened from content.
+ */
+
+const PAGE = `data:text/html,<a id="target" href="%23" onclick="window.open('http://www.example.com', '_blank', 'width=100,height=100');">Click me</a>`;
+
+/**
+ * Returns a Promise that resolves when a new window has
+ * opened, and the "load" event has fired in that window.
+ * We can't use BrowserTestUtils.domWindowOpened directly,
+ * because by the time the "then" on the Promise runs,
+ * DOMContentLoaded and load may have already run in the new
+ * window. However, we want to be very explicit about what
+ * events we're waiting for, and not rely on a quirk of our
+ * Promises infrastructure.
+ */
+function promiseNewWindow() {
+ return new Promise((resolve) => {
+ let observer = (subject, topic, data) => {
+ if (topic == "domwindowopened") {
+ Services.ww.unregisterNotification(observer);
+ let win = subject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ resolve(win);
+ });
+ }
+ };
+
+ Services.ww.registerNotification(observer);
+ });
+}
+
+/**
+ * Test that when a new window is opened from content, focus moves
+ * to the initial browser in that window once the window has finished
+ * painting.
+ */
+add_task(function* test_focus_browser() {
+ yield BrowserTestUtils.withNewTab({
+ url: PAGE,
+ gBrowser,
+ }, function*(browser) {
+ let newWinPromise = promiseNewWindow();
+ let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser);
+ let newWin = yield newWinPromise;
+ yield BrowserTestUtils.contentPainted(newWin.gBrowser.selectedBrowser);
+ yield delayedStartupPromise;
+
+ let focusedElement =
+ Services.focus.getFocusedElementForWindow(newWin, false, {});
+
+ Assert.equal(focusedElement, newWin.gBrowser.selectedBrowser,
+ "Initial browser should be focused");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ });
+});
+
+/**
+ * Test that when a new window is opened from content and focus
+ * shifts in that window before the content has a chance to paint
+ * that we _don't_ steal focus once content has painted.
+ */
+add_task(function* test_no_steal_focus() {
+ yield BrowserTestUtils.withNewTab({
+ url: PAGE,
+ gBrowser,
+ }, function*(browser) {
+ let newWinPromise = promiseNewWindow();
+ let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser);
+ let newWin = yield newWinPromise;
+
+ // Because we're switching focus, we shouldn't steal it once
+ // content paints.
+ newWin.gURLBar.focus();
+
+ yield BrowserTestUtils.contentPainted(newWin.gBrowser.selectedBrowser);
+ yield delayedStartupPromise;
+
+ let focusedElement =
+ Services.focus.getFocusedElementForWindow(newWin, false, {});
+
+ Assert.equal(focusedElement, newWin.gURLBar.inputField,
+ "URLBar should be focused");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ });
+});
diff --git a/browser/base/content/test/general/browser_no_mcb_on_http_site.js b/browser/base/content/test/general/browser_no_mcb_on_http_site.js
new file mode 100644
index 000000000..45fd67379
--- /dev/null
+++ b/browser/base/content/test/general/browser_no_mcb_on_http_site.js
@@ -0,0 +1,106 @@
+/*
+ * Description of the Tests for
+ * - Bug 909920 - Mixed content warning should not show on a HTTP site
+ *
+ * Description of the tests:
+ * Test 1:
+ * 1) Load an http page
+ * 2) The page includes a css file using https
+ * 3) The css file loads an |IMAGE| << over http
+ *
+ * Test 2:
+ * 1) Load an http page
+ * 2) The page includes a css file using https
+ * 3) The css file loads a |FONT| over http
+ *
+ * Test 3:
+ * 1) Load an http page
+ * 2) The page includes a css file using https
+ * 3) The css file imports (@import) another css file using http
+ * 3) The imported css file loads a |FONT| over http
+*
+ * Since the top-domain is >> NOT << served using https, the MCB
+ * should >> NOT << trigger a warning.
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+
+const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/";
+
+var gTestBrowser = null;
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+}
+
+add_task(function* init() {
+ yield SpecialPowers.pushPrefEnv({ set: [[ PREF_ACTIVE, true ],
+ [ PREF_DISPLAY, true ]] });
+ let url = gHttpTestRoot + "test_no_mcb_on_http_site_img.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url)
+ gTestBrowser = tab.linkedBrowser;
+});
+
+// ------------- TEST 1 -----------------------------------------
+
+add_task(function* test1() {
+ let expected = "Verifying MCB does not trigger warning/error for an http page ";
+ expected += "with https css that includes http image";
+
+ yield ContentTask.spawn(gTestBrowser, expected, function* (condition) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("testDiv").innerHTML == condition,
+ "Waited too long for status in Test 1!");
+ });
+
+ // Explicit OKs needed because the harness requires at least one call to ok.
+ ok(true, "test 1 passed");
+
+ // set up test 2
+ let url = gHttpTestRoot + "test_no_mcb_on_http_site_font.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+// ------------- TEST 2 -----------------------------------------
+
+add_task(function* test2() {
+ let expected = "Verifying MCB does not trigger warning/error for an http page ";
+ expected += "with https css that includes http font";
+
+ yield ContentTask.spawn(gTestBrowser, expected, function* (condition) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("testDiv").innerHTML == condition,
+ "Waited too long for status in Test 2!");
+ });
+
+ ok(true, "test 2 passed");
+
+ // set up test 3
+ let url = gHttpTestRoot + "test_no_mcb_on_http_site_font2.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+// ------------- TEST 3 -----------------------------------------
+
+add_task(function* test3() {
+ let expected = "Verifying MCB does not trigger warning/error for an http page "
+ expected += "with https css that imports another http css which includes http font";
+
+ yield ContentTask.spawn(gTestBrowser, expected, function* (condition) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("testDiv").innerHTML == condition,
+ "Waited too long for status in Test 3!");
+ });
+
+ ok(true, "test3 passed");
+});
+
+// ------------------------------------------------------
+
+add_task(function* cleanup() {
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_offlineQuotaNotification.js b/browser/base/content/test/general/browser_offlineQuotaNotification.js
new file mode 100644
index 000000000..e56bfe9a8
--- /dev/null
+++ b/browser/base/content/test/general/browser_offlineQuotaNotification.js
@@ -0,0 +1,95 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test offline quota warnings - must be run as a mochitest-browser test or
+// else the test runner gets in the way of notifications due to bug 857897.
+
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/offlineQuotaNotification.html";
+
+registerCleanupFunction(function() {
+ // Clean up after ourself
+ let uri = Services.io.newURI(URL, null, null);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.perms.removeFromPrincipal(principal, "offline-app");
+ Services.prefs.clearUserPref("offline-apps.quota.warn");
+ Services.prefs.clearUserPref("offline-apps.allow_by_default");
+ let {OfflineAppCacheHelper} = Components.utils.import("resource:///modules/offlineAppCache.jsm", {});
+ OfflineAppCacheHelper.clear();
+});
+
+// Same as the other one, but for in-content preferences
+function checkInContentPreferences(win) {
+ let doc = win.document;
+ let sel = doc.getElementById("categories").selectedItems[0].id;
+ let tab = doc.getElementById("advancedPrefs").selectedTab.id;
+ is(gBrowser.currentURI.spec, "about:preferences#advanced", "about:preferences loaded");
+ is(sel, "category-advanced", "Advanced pane was selected");
+ is(tab, "networkTab", "Network tab is selected");
+ // all good, we are done.
+ win.close();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("offline-apps.allow_by_default", false);
+
+ // Open a new tab.
+ gBrowser.selectedTab = gBrowser.addTab(URL);
+ registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+
+ Promise.all([
+ // Wait for a notification that asks whether to allow offline storage.
+ promiseNotification(),
+ // Wait for the tab to load.
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser),
+ ]).then(() => {
+ info("Loaded page, adding onCached handler");
+ // Need a promise to keep track of when we've added our handler.
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let onCachedAttached = BrowserTestUtils.waitForMessage(mm, "Test:OnCachedAttached");
+ let gotCached = ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ return new Promise(resolve => {
+ content.window.applicationCache.oncached = function() {
+ setTimeout(resolve, 0);
+ };
+ sendAsyncMessage("Test:OnCachedAttached");
+ });
+ });
+ gotCached.then(function() {
+ // We got cached - now we should have provoked the quota warning.
+ let notification = PopupNotifications.getNotification('offline-app-usage');
+ ok(notification, "have offline-app-usage notification");
+ // select the default action - this should cause the preferences
+ // tab to open - which we track via an "Initialized" event.
+ PopupNotifications.panel.firstElementChild.button.click();
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function PrefInit() {
+ newTabBrowser.removeEventListener("Initialized", PrefInit, true);
+ executeSoon(function() {
+ checkInContentPreferences(newTabBrowser.contentWindow);
+ })
+ }, true);
+ });
+ onCachedAttached.then(function() {
+ Services.prefs.setIntPref("offline-apps.quota.warn", 1);
+
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates which will call our oncached handler above.
+ PopupNotifications.panel.firstElementChild.button.click();
+ });
+ });
+}
+
+function promiseNotification() {
+ return new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("popupshown", function onShown() {
+ PopupNotifications.panel.removeEventListener("popupshown", onShown);
+ resolve();
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_overflowScroll.js b/browser/base/content/test/general/browser_overflowScroll.js
new file mode 100644
index 000000000..56932fae2
--- /dev/null
+++ b/browser/base/content/test/general/browser_overflowScroll.js
@@ -0,0 +1,91 @@
+var tabstrip = gBrowser.tabContainer.mTabstrip;
+var scrollbox = tabstrip._scrollbox;
+var originalSmoothScroll = tabstrip.smoothScroll;
+var tabs = gBrowser.tabs;
+
+var rect = ele => ele.getBoundingClientRect();
+var width = ele => rect(ele).width;
+var left = ele => rect(ele).left;
+var right = ele => rect(ele).right;
+var isLeft = (ele, msg) => is(left(ele) + tabstrip._tabMarginLeft, left(scrollbox), msg);
+var isRight = (ele, msg) => is(right(ele) - tabstrip._tabMarginRight, right(scrollbox), msg);
+var elementFromPoint = x => tabstrip._elementFromPoint(x);
+var nextLeftElement = () => elementFromPoint(left(scrollbox) - 1);
+var nextRightElement = () => elementFromPoint(right(scrollbox) + 1);
+var firstScrollable = () => tabs[gBrowser._numPinnedTabs];
+
+function test() {
+ requestLongerTimeout(2);
+ waitForExplicitFinish();
+
+ // If the previous (or more) test finished with cleaning up the tabs,
+ // there may be some pending animations. That can cause a failure of
+ // this tests, so, we should test this in another stack.
+ setTimeout(doTest, 0);
+}
+
+function doTest() {
+ tabstrip.smoothScroll = false;
+
+ var tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
+ var tabCountForOverflow = Math.ceil(width(tabstrip) / tabMinWidth * 3);
+ while (tabs.length < tabCountForOverflow)
+ gBrowser.addTab("about:blank", {skipAnimation: true});
+ gBrowser.pinTab(tabs[0]);
+
+ tabstrip.addEventListener("overflow", runOverflowTests, false);
+}
+
+function runOverflowTests(aEvent) {
+ if (aEvent.detail != 1)
+ return;
+
+ tabstrip.removeEventListener("overflow", runOverflowTests, false);
+
+ var upButton = tabstrip._scrollButtonUp;
+ var downButton = tabstrip._scrollButtonDown;
+ var element;
+
+ gBrowser.selectedTab = firstScrollable();
+ ok(left(scrollbox) <= left(firstScrollable()), "Selecting the first tab scrolls it into view " +
+ "(" + left(scrollbox) + " <= " + left(firstScrollable()) + ")");
+
+ element = nextRightElement();
+ EventUtils.synthesizeMouseAtCenter(downButton, {});
+ isRight(element, "Scrolled one tab to the right with a single click");
+
+ gBrowser.selectedTab = tabs[tabs.length - 1];
+ ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " +
+ "(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")");
+
+ element = nextLeftElement();
+ EventUtils.synthesizeMouse(upButton, 1, 1, {});
+ isLeft(element, "Scrolled one tab to the left with a single click");
+
+ let elementPoint = left(scrollbox) - width(scrollbox);
+ element = elementFromPoint(elementPoint);
+ if (elementPoint == right(element)) {
+ element = element.nextSibling;
+ }
+ EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2});
+ isLeft(element, "Scrolled one page of tabs with a double click");
+
+ EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 3});
+ var firstScrollableLeft = left(firstScrollable());
+ ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " +
+ "(" + left(scrollbox) + " <= " + firstScrollableLeft + ")");
+
+ for (var i = 2; i; i--)
+ EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE });
+ is(left(firstScrollable()), firstScrollableLeft, "Remained at the start with the mouse wheel");
+
+ element = nextRightElement();
+ EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE});
+ isRight(element, "Scrolled one tab to the right with the mouse wheel");
+
+ while (tabs.length > 1)
+ gBrowser.removeTab(tabs[0]);
+
+ tabstrip.smoothScroll = originalSmoothScroll;
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_pageInfo.js b/browser/base/content/test/general/browser_pageInfo.js
new file mode 100644
index 000000000..90fe2e17f
--- /dev/null
+++ b/browser/base/content/test/general/browser_pageInfo.js
@@ -0,0 +1,38 @@
+function test() {
+ waitForExplicitFinish();
+
+ var pageInfo;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
+
+ Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
+ pageInfo = BrowserPageInfo();
+ }, true);
+ content.location =
+ "https://example.com/browser/browser/base/content/test/general/feed_tab.html";
+
+ function observer(win, topic, data) {
+ Services.obs.removeObserver(observer, "page-info-dialog-loaded");
+ pageInfo.onFinished.push(handlePageInfo);
+ }
+
+ function handlePageInfo() {
+ ok(pageInfo.document.getElementById("feedTab"), "Feed tab");
+ let feedListbox = pageInfo.document.getElementById("feedListbox");
+ ok(feedListbox, "Feed list");
+
+ var feedRowsNum = feedListbox.getRowCount();
+ is(feedRowsNum, 3, "Number of feeds listed");
+
+ for (var i = 0; i < feedRowsNum; i++) {
+ let feedItem = feedListbox.getItemAtIndex(i);
+ is(feedItem.getAttribute("name"), i + 1, "Feed name");
+ }
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}
diff --git a/browser/base/content/test/general/browser_page_style_menu.js b/browser/base/content/test/general/browser_page_style_menu.js
new file mode 100644
index 000000000..cb080d52a
--- /dev/null
+++ b/browser/base/content/test/general/browser_page_style_menu.js
@@ -0,0 +1,97 @@
+"use strict";
+
+/**
+ * Stylesheets are updated for a browser after the pageshow event.
+ * This helper function returns a Promise that waits for that pageshow
+ * event, and then resolves on the next tick to ensure that gPageStyleMenu
+ * has had a chance to update the stylesheets.
+ *
+ * @param browser
+ * The <xul:browser> to wait for.
+ * @return Promise
+ */
+function promiseStylesheetsUpdated(browser) {
+ return ContentTask.spawn(browser, { PAGE }, function*(args) {
+ return new Promise((resolve) => {
+ addEventListener("pageshow", function onPageShow(e) {
+ if (e.target.location == args.PAGE) {
+ removeEventListener("pageshow", onPageShow);
+ content.setTimeout(resolve, 0);
+ }
+ });
+ })
+ });
+}
+
+const PAGE = "http://example.com/browser/browser/base/content/test/general/page_style_sample.html";
+
+/*
+ * Test that the right stylesheets do (and others don't) show up
+ * in the page style menu.
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false);
+ let browser = tab.linkedBrowser;
+ yield BrowserTestUtils.loadURI(browser, PAGE);
+ yield promiseStylesheetsUpdated(browser);
+
+ let menupopup = document.getElementById("pageStyleMenu").menupopup;
+ gPageStyleMenu.fillPopup(menupopup);
+
+ var items = [];
+ var current = menupopup.getElementsByTagName("menuseparator")[0];
+ while (current.nextSibling) {
+ current = current.nextSibling;
+ items.push(current);
+ }
+
+ items = items.map(el => ({
+ label: el.getAttribute("label"),
+ checked: el.getAttribute("checked") == "true",
+ }));
+
+ let validLinks = yield ContentTask.spawn(gBrowser.selectedBrowser, items, function(contentItems) {
+ let contentValidLinks = 0;
+ Array.forEach(content.document.querySelectorAll("link, style"), function (el) {
+ var title = el.getAttribute("title");
+ var rel = el.getAttribute("rel");
+ var media = el.getAttribute("media");
+ var idstring = el.nodeName + " " + (title ? title : "without title and") +
+ " with rel=\"" + rel + "\"" +
+ (media ? " and media=\"" + media + "\"" : "");
+
+ var item = contentItems.filter(aItem => aItem.label == title);
+ var found = item.length == 1;
+ var checked = found && item[0].checked;
+
+ switch (el.getAttribute("data-state")) {
+ case "0":
+ ok(!found, idstring + " should not show up in page style menu");
+ break;
+ case "0-todo":
+ contentValidLinks++;
+ todo(!found, idstring + " should not show up in page style menu");
+ ok(!checked, idstring + " should not be selected");
+ break;
+ case "1":
+ contentValidLinks++;
+ ok(found, idstring + " should show up in page style menu");
+ ok(!checked, idstring + " should not be selected");
+ break;
+ case "2":
+ contentValidLinks++;
+ ok(found, idstring + " should show up in page style menu");
+ ok(checked, idstring + " should be selected");
+ break;
+ default:
+ throw "data-state attribute is missing or has invalid value";
+ }
+ });
+ return contentValidLinks;
+ });
+
+ ok(items.length, "At least one item in the menu");
+ is(items.length, validLinks, "all valid links found");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_page_style_menu_update.js b/browser/base/content/test/general/browser_page_style_menu_update.js
new file mode 100644
index 000000000..a0c741e48
--- /dev/null
+++ b/browser/base/content/test/general/browser_page_style_menu_update.js
@@ -0,0 +1,67 @@
+"use strict";
+
+const PAGE = "http://example.com/browser/browser/base/content/test/general/page_style_sample.html";
+
+/**
+ * Stylesheets are updated for a browser after the pageshow event.
+ * This helper function returns a Promise that waits for that pageshow
+ * event, and then resolves on the next tick to ensure that gPageStyleMenu
+ * has had a chance to update the stylesheets.
+ *
+ * @param browser
+ * The <xul:browser> to wait for.
+ * @return Promise
+ */
+function promiseStylesheetsUpdated(browser) {
+ return ContentTask.spawn(browser, { PAGE }, function*(args) {
+ return new Promise((resolve) => {
+ addEventListener("pageshow", function onPageShow(e) {
+ if (e.target.location == args.PAGE) {
+ removeEventListener("pageshow", onPageShow);
+ content.setTimeout(resolve, 0);
+ }
+ });
+ })
+ });
+}
+
+/**
+ * Tests that the Page Style menu shows the currently
+ * selected Page Style after a new one has been selected.
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false);
+ let browser = tab.linkedBrowser;
+
+ yield BrowserTestUtils.loadURI(browser, PAGE);
+ yield promiseStylesheetsUpdated(browser);
+
+ let menupopup = document.getElementById("pageStyleMenu").menupopup;
+ gPageStyleMenu.fillPopup(menupopup);
+
+ // page_style_sample.html should default us to selecting the stylesheet
+ // with the title "6" first.
+ let selected = menupopup.querySelector("menuitem[checked='true']");
+ is(selected.getAttribute("label"), "6", "Should have '6' stylesheet selected by default");
+
+ // Now select stylesheet "1"
+ let target = menupopup.querySelector("menuitem[label='1']");
+ target.click();
+
+ // Now we need to wait for the content process to send its stylesheet
+ // update for the selected tab to the parent. Because messages are
+ // guaranteed to be sent in order, we'll make sure we do the check
+ // after the parent has been updated by yielding until the child
+ // has finished running a ContentTask for us.
+ yield ContentTask.spawn(browser, {}, function*() {
+ dump('\nJust wasting some time.\n');
+ });
+
+ gPageStyleMenu.fillPopup(menupopup);
+ // gPageStyleMenu empties out the menu between opens, so we need
+ // to get a new reference to the selected menuitem
+ selected = menupopup.querySelector("menuitem[checked='true']");
+ is(selected.getAttribute("label"), "1", "Should now have stylesheet 1 selected");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_pageinfo_svg_image.js b/browser/base/content/test/general/browser_pageinfo_svg_image.js
new file mode 100644
index 000000000..02514d79f
--- /dev/null
+++ b/browser/base/content/test/general/browser_pageinfo_svg_image.js
@@ -0,0 +1,38 @@
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
+ var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab");
+
+ pageInfo.addEventListener("load", function loadListener2() {
+ pageInfo.removeEventListener("load", loadListener2, true);
+ pageInfo.onFinished.push(function() {
+ executeSoon(function() {
+ var imageTree = pageInfo.document.getElementById("imagetree");
+ var imageRowsNum = imageTree.view.rowCount;
+
+ ok(imageTree, "Image tree is null (media tab is broken)");
+
+ is(imageRowsNum, 1, "should have one image");
+
+ // Only bother running this if we've got the right number of rows.
+ if (imageRowsNum == 1) {
+ is(imageTree.view.getCellText(0, imageTree.columns[0]),
+ "https://example.com/browser/browser/base/content/test/general/title_test.svg",
+ "The URL should be the svg image.");
+ }
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "https://example.com/browser/browser/base/content/test/general/svg_image.html";
+}
diff --git a/browser/base/content/test/general/browser_parsable_css.js b/browser/base/content/test/general/browser_parsable_css.js
new file mode 100644
index 000000000..72954d2e5
--- /dev/null
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -0,0 +1,376 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' CSS issues to remain, while we
+ * detect newly occurring issues in shipping CSS. It is a list of objects
+ * specifying conditions under which an error should be ignored.
+ *
+ * Every property of the objects in it needs to consist of a regular expression
+ * matching the offending error. If an object has multiple regex criteria, they
+ * ALL need to match an error in order for that error not to cause a test
+ * failure. */
+let whitelist = [
+ // CodeMirror is imported as-is, see bug 1004423.
+ {sourceName: /codemirror\.css$/i,
+ isFromDevTools: true},
+ // The debugger uses cross-browser CSS.
+ {sourceName: /devtools\/client\/debugger\/new\/styles.css/i,
+ isFromDevTools: true},
+ // PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
+ {sourceName: /web\/viewer\.css$/i,
+ errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i,
+ isFromDevTools: false},
+ // Tracked in bug 1004428.
+ {sourceName: /aboutaccounts\/(main|normalize)\.css$/i,
+ isFromDevTools: false},
+ // Highlighter CSS uses a UA-only pseudo-class, see bug 985597.
+ {sourceName: /highlighters\.css$/i,
+ errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i,
+ isFromDevTools: true},
+ // Responsive Design Mode CSS uses a UA-only pseudo-class, see Bug 1241714.
+ {sourceName: /responsive-ua\.css$/i,
+ errorMessage: /Unknown pseudo-class.*moz-dropdown-list/i,
+ isFromDevTools: true},
+
+ {sourceName: /\b(contenteditable|EditorOverride|svg|forms|html|mathml|ua)\.css$/i,
+ errorMessage: /Unknown pseudo-class.*-moz-/i,
+ isFromDevTools: false},
+ {sourceName: /\b(html|mathml|ua)\.css$/i,
+ errorMessage: /Unknown property.*-moz-/i,
+ isFromDevTools: false},
+ // Reserved to UA sheets unless layout.css.overflow-clip-box.enabled flipped to true.
+ {sourceName: /res\/forms\.css$/i,
+ errorMessage: /Unknown property.*overflow-clip-box/i,
+ isFromDevTools: false},
+ {sourceName: /res\/(ua|html)\.css$/i,
+ errorMessage: /Unknown pseudo-class .*\bfullscreen\b/i,
+ isFromDevTools: false},
+ {sourceName: /skin\/timepicker\.css$/i,
+ errorMessage: /Error in parsing.*mask/i,
+ isFromDevTools: false},
+];
+
+// Platform can be "linux", "macosx" or "win". If omitted, the exception applies to all platforms.
+let allowedImageReferences = [
+ // Bug 1302691
+ {file: "chrome://devtools/skin/images/dock-bottom-minimize@2x.png",
+ from: "chrome://devtools/skin/toolbox.css",
+ isFromDevTools: true},
+ {file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png",
+ from: "chrome://devtools/skin/toolbox.css",
+ isFromDevTools: true},
+];
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+// Add suffix to stylesheets' URI so that we always load them here and
+// have them parsed. Add a random number so that even if we run this
+// test multiple times, it would be unlikely to affect each other.
+const kPathSuffix = "?always-parse-css-" + Math.random();
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in whitelist
+ *
+ * @param aErrorObject the error to check
+ * @return true if the error should be ignored, false otherwise.
+ */
+function ignoredError(aErrorObject) {
+ for (let whitelistItem of whitelist) {
+ let matches = true;
+ for (let prop of ["sourceName", "errorMessage"]) {
+ if (whitelistItem.hasOwnProperty(prop) &&
+ !whitelistItem[prop].test(aErrorObject[prop] || "")) {
+ matches = false;
+ break;
+ }
+ }
+ if (matches) {
+ whitelistItem.used = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+function once(target, name) {
+ return new Promise((resolve, reject) => {
+ let cb = () => {
+ target.removeEventListener(name, cb);
+ resolve();
+ };
+ target.addEventListener(name, cb);
+ });
+}
+
+function fetchFile(uri) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.responseType = "text";
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (this.readyState != this.DONE) {
+ return;
+ }
+ try {
+ resolve(this.responseText);
+ } catch (ex) {
+ ok(false, `Script error reading ${uri}: ${ex}`);
+ resolve("");
+ }
+ };
+ xhr.onerror = error => {
+ ok(false, `XHR error reading ${uri}: ${error}`);
+ resolve("");
+ };
+ xhr.send(null);
+ });
+}
+
+var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIChromeRegistry);
+var gChromeMap = new Map();
+
+function getBaseUriForChromeUri(chromeUri) {
+ let chromeFile = chromeUri + "gobbledygooknonexistentfile.reallynothere";
+ let uri = Services.io.newURI(chromeFile, null, null);
+ let fileUri = gChromeReg.convertChromeURL(uri);
+ return fileUri.resolve(".");
+}
+
+function parseManifest(manifestUri) {
+ return fetchFile(manifestUri.spec).then(data => {
+ for (let line of data.split('\n')) {
+ let [type, ...argv] = line.split(/\s+/);
+ let component;
+ if (type == "content" || type == "skin") {
+ [component] = argv;
+ } else {
+ // skip unrelated lines
+ continue;
+ }
+ let chromeUri = `chrome://${component}/${type}/`;
+ gChromeMap.set(getBaseUriForChromeUri(chromeUri), chromeUri);
+ }
+ });
+}
+
+function convertToChromeUri(fileUri) {
+ let baseUri = fileUri.spec;
+ let path = "";
+ while (true) {
+ let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2);
+ if (slashPos < 0) {
+ info(`File not accessible from chrome protocol: ${fileUri.path}`);
+ return fileUri;
+ }
+ path = baseUri.slice(slashPos + 1) + path;
+ baseUri = baseUri.slice(0, slashPos + 1);
+ if (gChromeMap.has(baseUri)) {
+ let chromeBaseUri = gChromeMap.get(baseUri);
+ let chromeUri = `${chromeBaseUri}${path}`;
+ return Services.io.newURI(chromeUri, null, null);
+ }
+ }
+}
+
+function messageIsCSSError(msg) {
+ // Only care about CSS errors generated by our iframe:
+ if ((msg instanceof Ci.nsIScriptError) &&
+ msg.category.includes("CSS") &&
+ msg.sourceName.endsWith(kPathSuffix)) {
+ let sourceName = msg.sourceName.slice(0, -kPathSuffix.length);
+ let msgInfo = { sourceName, errorMessage: msg.errorMessage };
+ // Check if this error is whitelisted in whitelist
+ if (!ignoredError(msgInfo)) {
+ ok(false, `Got error message for ${sourceName}: ${msg.errorMessage}`);
+ return true;
+ }
+ info(`Ignored error for ${sourceName} because of filter.`);
+ }
+ return false;
+}
+
+let imageURIsToReferencesMap = new Map();
+
+function processCSSRules(sheet) {
+ for (let rule of sheet.cssRules) {
+ if (rule instanceof CSSMediaRule) {
+ processCSSRules(rule);
+ continue;
+ }
+ if (!(rule instanceof CSSStyleRule))
+ continue;
+
+ // Extract urls from the css text.
+ // Note: CSSStyleRule.cssText always has double quotes around URLs even
+ // when the original CSS file didn't.
+ let urls = rule.cssText.match(/url\("[^"]*"\)/g);
+ if (!urls)
+ continue;
+
+ for (let url of urls) {
+ // Remove the url(" prefix and the ") suffix.
+ url = url.replace(/url\("(.*)"\)/, "$1");
+ if (url.startsWith("data:"))
+ continue;
+
+ // Make the url absolute and remove the ref.
+ let baseURI = Services.io.newURI(rule.parentStyleSheet.href, null, null);
+ url = Services.io.newURI(url, null, baseURI).specIgnoringRef;
+
+ // Store the image url along with the css file referencing it.
+ let baseUrl = baseURI.spec.split("?always-parse-css")[0];
+ if (!imageURIsToReferencesMap.has(url)) {
+ imageURIsToReferencesMap.set(url, new Set([baseUrl]));
+ } else {
+ imageURIsToReferencesMap.get(url).add(baseUrl);
+ }
+ }
+ }
+}
+
+function chromeFileExists(aURI)
+{
+ let available = 0;
+ try {
+ let channel = NetUtil.newChannel({uri: aURI, loadUsingSystemPrincipal: true});
+ let stream = channel.open();
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sstream.init(stream);
+ available = sstream.available();
+ sstream.close();
+ } catch (e) {
+ if (e.result != Components.results.NS_ERROR_FILE_NOT_FOUND) {
+ dump("Checking " + aURI + ": " + e + "\n");
+ Cu.reportError(e);
+ }
+ }
+ return available > 0;
+}
+
+add_task(function* checkAllTheCSS() {
+ let appDir = Services.dirsvc.get("GreD", Ci.nsIFile);
+ // This asynchronously produces a list of URLs (sadly, mostly sync on our
+ // test infrastructure because it runs against jarfiles there, and
+ // our zipreader APIs are all sync)
+ let uris = yield generateURIsFromDirTree(appDir, [".css", ".manifest"]);
+
+ // Create a clean iframe to load all the files into. This needs to live at a
+ // chrome URI so that it's allowed to load and parse any styles.
+ let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
+ let windowless = Services.appShell.createWindowlessBrowser();
+ let iframe = windowless.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
+ windowless.document.documentElement.appendChild(iframe);
+ let iframeLoaded = once(iframe, 'load');
+ iframe.contentWindow.location = testFile;
+ yield iframeLoaded;
+ let doc = iframe.contentWindow.document;
+
+ // Parse and remove all manifests from the list.
+ // NOTE that this must be done before filtering out devtools paths
+ // so that all chrome paths can be recorded.
+ let manifestPromises = [];
+ uris = uris.filter(uri => {
+ if (uri.path.endsWith(".manifest")) {
+ manifestPromises.push(parseManifest(uri));
+ return false;
+ }
+ return true;
+ });
+ // Wait for all manifest to be parsed
+ yield Promise.all(manifestPromises);
+
+ // We build a list of promises that get resolved when their respective
+ // files have loaded and produced no errors.
+ let allPromises = [];
+
+ // filter out either the devtools paths or the non-devtools paths:
+ let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
+ let devtoolsPathBits = ["webide", "devtools"];
+ uris = uris.filter(uri => isDevtools == devtoolsPathBits.some(path => uri.spec.includes(path)));
+
+ for (let uri of uris) {
+ let linkEl = doc.createElement("link");
+ linkEl.setAttribute("rel", "stylesheet");
+ let promiseForThisSpec = Promise.defer();
+ let onLoad = (e) => {
+ processCSSRules(linkEl.sheet);
+ promiseForThisSpec.resolve();
+ linkEl.removeEventListener("load", onLoad);
+ linkEl.removeEventListener("error", onError);
+ };
+ let onError = (e) => {
+ ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
+ promiseForThisSpec.resolve();
+ linkEl.removeEventListener("load", onLoad);
+ linkEl.removeEventListener("error", onError);
+ };
+ linkEl.addEventListener("load", onLoad);
+ linkEl.addEventListener("error", onError);
+ linkEl.setAttribute("type", "text/css");
+ let chromeUri = convertToChromeUri(uri);
+ linkEl.setAttribute("href", chromeUri.spec + kPathSuffix);
+ allPromises.push(promiseForThisSpec.promise);
+ doc.head.appendChild(linkEl);
+ }
+
+ // Wait for all the files to have actually loaded:
+ yield Promise.all(allPromises);
+
+ // Check if all the files referenced from CSS actually exist.
+ for (let [image, references] of imageURIsToReferencesMap) {
+ if (!chromeFileExists(image)) {
+ for (let ref of references) {
+ let ignored = false;
+ for (let item of allowedImageReferences) {
+ if (image.endsWith(item.file) && ref.endsWith(item.from) &&
+ isDevtools == item.isFromDevTools &&
+ (!item.platforms || item.platforms.includes(AppConstants.platform))) {
+ item.used = true;
+ ignored = true;
+ break;
+ }
+ }
+ if (!ignored)
+ ok(false, "missing " + image + " referenced from " + ref);
+ }
+ }
+ }
+
+ let messages = Services.console.getMessageArray();
+ // Count errors (the test output will list actual issues for us, as well
+ // as the ok(false) in messageIsCSSError.
+ let errors = messages.filter(messageIsCSSError);
+ is(errors.length, 0, "All the styles (" + allPromises.length + ") loaded without errors.");
+
+ // Confirm that all whitelist rules have been used.
+ for (let item of whitelist) {
+ if (!item.used && isDevtools == item.isFromDevTools) {
+ ok(false, "Unused whitelist item. " +
+ (item.sourceName ? " sourceName: " + item.sourceName : "") +
+ (item.errorMessage ? " errorMessage: " + item.errorMessage : ""));
+ }
+ }
+
+ // Confirm that all file whitelist rules have been used.
+ for (let item of allowedImageReferences) {
+ if (!item.used && isDevtools == item.isFromDevTools &&
+ (!item.platforms || item.platforms.includes(AppConstants.platform))) {
+ ok(false, "Unused file whitelist item. " +
+ " file: " + item.file +
+ " from: " + item.from);
+ }
+ }
+
+ // Clean up to avoid leaks:
+ iframe.remove();
+ doc.head.innerHTML = '';
+ doc = null;
+ iframe = null;
+ windowless.close();
+ windowless = null;
+ imageURIsToReferencesMap = null;
+});
diff --git a/browser/base/content/test/general/browser_parsable_script.js b/browser/base/content/test/general/browser_parsable_script.js
new file mode 100644
index 000000000..50333dd65
--- /dev/null
+++ b/browser/base/content/test/general/browser_parsable_script.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' JS issues to remain, while we
+ * detect newly occurring issues in shipping JS. It is a list of regexes
+ * matching files which have errors:
+ */
+const kWhitelist = new Set([
+ /defaults\/profile\/prefs.js$/,
+ /browser\/content\/browser\/places\/controller.js$/,
+]);
+
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+// Normally we would use reflect.jsm to get Reflect.parse. However, if
+// we do that, then all the AST data is allocated in reflect.jsm's
+// zone. That exposes a bug in our GC. The GC collects reflect.jsm's
+// zone but not the zone in which our test code lives (since no new
+// data is being allocated in it). The cross-compartment wrappers in
+// our zone that point to the AST data never get collected, and so the
+// AST data itself is never collected. We need to GC both zones at
+// once to fix the problem.
+const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
+init();
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in kWhitelist
+ *
+ * @param uri the uri to check against the whitelist
+ * @return true if the uri should be skipped, false otherwise.
+ */
+function uriIsWhiteListed(uri) {
+ for (let whitelistItem of kWhitelist) {
+ if (whitelistItem.test(uri.spec)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function parsePromise(uri) {
+ let promise = new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ let scriptText = this.responseText;
+ try {
+ info("Checking " + uri);
+ Reflect.parse(scriptText);
+ resolve(true);
+ } catch (ex) {
+ let errorMsg = "Script error reading " + uri + ": " + ex;
+ ok(false, errorMsg);
+ resolve(false);
+ }
+ }
+ };
+ xhr.onerror = (error) => {
+ ok(false, "XHR error reading " + uri + ": " + error);
+ resolve(false);
+ };
+ xhr.overrideMimeType("application/javascript");
+ xhr.send(null);
+ });
+ return promise;
+}
+
+add_task(function* checkAllTheJS() {
+ // In debug builds, even on a fast machine, collecting the file list may take
+ // more than 30 seconds, and parsing all files may take four more minutes.
+ // For this reason, this test must be explictly requested in debug builds by
+ // using the "--setpref parse=<filter>" argument to mach. You can specify:
+ // - A case-sensitive substring of the file name to test (slow).
+ // - A single absolute URI printed out by a previous run (fast).
+ // - An empty string to run the test on all files (slowest).
+ let parseRequested = Services.prefs.prefHasUserValue("parse");
+ let parseValue = parseRequested && Services.prefs.getCharPref("parse");
+ if (SpecialPowers.isDebugBuild) {
+ if (!parseRequested) {
+ ok(true, "Test disabled on debug build. To run, execute: ./mach" +
+ " mochitest-browser --setpref parse=<case_sensitive_filter>" +
+ " browser/base/content/test/general/browser_parsable_script.js");
+ return;
+ }
+ // Request a 15 minutes timeout (30 seconds * 30) for debug builds.
+ requestLongerTimeout(30);
+ }
+
+ let uris;
+ // If an absolute URI is specified on the command line, use it immediately.
+ if (parseValue && parseValue.includes(":")) {
+ uris = [NetUtil.newURI(parseValue)];
+ } else {
+ let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+ // This asynchronously produces a list of URLs (sadly, mostly sync on our
+ // test infrastructure because it runs against jarfiles there, and
+ // our zipreader APIs are all sync)
+ let startTimeMs = Date.now();
+ info("Collecting URIs");
+ uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]);
+ info("Collected URIs in " + (Date.now() - startTimeMs) + "ms");
+
+ // Apply the filter specified on the command line, if any.
+ if (parseValue) {
+ uris = uris.filter(uri => {
+ if (uri.spec.includes(parseValue)) {
+ return true;
+ }
+ info("Not checking filtered out " + uri.spec);
+ return false;
+ });
+ }
+ }
+
+ // We create an array of promises so we can parallelize all our parsing
+ // and file loading activity:
+ let allPromises = [];
+ for (let uri of uris) {
+ if (uriIsWhiteListed(uri)) {
+ info("Not checking whitelisted " + uri.spec);
+ continue;
+ }
+ allPromises.push(parsePromise(uri.spec));
+ }
+
+ let promiseResults = yield Promise.all(allPromises);
+ is(promiseResults.filter((x) => !x).length, 0, "There should be 0 parsing errors");
+});
diff --git a/browser/base/content/test/general/browser_permissions.js b/browser/base/content/test/general/browser_permissions.js
new file mode 100644
index 000000000..721a669d2
--- /dev/null
+++ b/browser/base/content/test/general/browser_permissions.js
@@ -0,0 +1,202 @@
+/*
+ * Test the Permissions section in the Control Center.
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PERMISSIONS_PAGE = "http://example.com/browser/browser/base/content/test/general/permissions.html";
+var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {});
+
+registerCleanupFunction(function() {
+ SitePermissions.remove(gBrowser.currentURI, "cookie");
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+ SitePermissions.remove(gBrowser.currentURI, "microphone");
+
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function* openIdentityPopup() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ gIdentityHandler._identityBox.click();
+ return promise;
+}
+
+function* closeIdentityPopup() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
+ gIdentityHandler._identityPopup.hidePopup();
+ return promise;
+}
+
+add_task(function* testMainViewVisible() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ let permissionsList = document.getElementById("identity-popup-permission-list");
+ let emptyLabel = permissionsList.nextSibling.nextSibling;
+
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyLabel), "List of permissions is empty");
+
+ yield closeIdentityPopup();
+
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
+
+ yield openIdentityPopup();
+
+ ok(is_hidden(emptyLabel), "List of permissions is not empty");
+
+ let labelText = SitePermissions.getPermissionLabel("camera");
+ let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+ is(labels.length, 1, "One permission visible in main view");
+ is(labels[0].textContent, labelText, "Correct value");
+
+ let img = permissionsList.querySelector("image.identity-popup-permission-icon");
+ ok(img, "There is an image for the permissions");
+ ok(img.classList.contains("camera-icon"), "proper class is in image class");
+
+ yield closeIdentityPopup();
+
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyLabel), "List of permissions is empty");
+
+ yield closeIdentityPopup();
+});
+
+add_task(function* testIdentityIcon() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
+
+ ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box signals granted permissions");
+
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+
+ ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box doesn't signal granted permissions");
+
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
+
+ ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box doesn't signal granted permissions");
+
+ SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.SESSION);
+
+ ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box signals granted permissions");
+
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+ SitePermissions.remove(gBrowser.currentURI, "cookie");
+});
+
+add_task(function* testCancelPermission() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ let permissionsList = document.getElementById("identity-popup-permission-list");
+ let emptyLabel = permissionsList.nextSibling.nextSibling;
+
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
+
+ yield openIdentityPopup();
+
+ ok(is_hidden(emptyLabel), "List of permissions is not empty");
+
+ let cancelButtons = permissionsList
+ .querySelectorAll(".identity-popup-permission-remove-button");
+
+ cancelButtons[0].click();
+ let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+ is(labels.length, 1, "One permission should be removed");
+ cancelButtons[1].click();
+ labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+ is(labels.length, 0, "One permission should be removed");
+
+ yield closeIdentityPopup();
+});
+
+add_task(function* testPermissionHints() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ let permissionsList = document.getElementById("identity-popup-permission-list");
+ let emptyHint = document.getElementById("identity-popup-permission-empty-hint");
+ let reloadHint = document.getElementById("identity-popup-permission-reload-hint");
+
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyHint), "Empty hint is visible");
+ ok(is_hidden(reloadHint), "Reload hint is hidden");
+
+ yield closeIdentityPopup();
+
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
+
+ yield openIdentityPopup();
+
+ ok(is_hidden(emptyHint), "Empty hint is hidden");
+ ok(is_hidden(reloadHint), "Reload hint is hidden");
+
+ let cancelButtons = permissionsList
+ .querySelectorAll(".identity-popup-permission-remove-button");
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+
+ cancelButtons[0].click();
+ ok(is_hidden(emptyHint), "Empty hint is hidden");
+ ok(!is_hidden(reloadHint), "Reload hint is visible");
+
+ cancelButtons[1].click();
+ ok(is_hidden(emptyHint), "Empty hint is hidden");
+ ok(!is_hidden(reloadHint), "Reload hint is visible");
+
+ yield closeIdentityPopup();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyHint), "Empty hint is visible after reloading");
+ ok(is_hidden(reloadHint), "Reload hint is hidden after reloading");
+
+ yield closeIdentityPopup();
+});
+
+add_task(function* testPermissionIcons() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK);
+ SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION);
+
+ let geoIcon = gIdentityHandler._identityBox
+ .querySelector(".blocked-permission-icon[data-permission-id='geo']");
+ ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
+
+ let cameraIcon = gIdentityHandler._identityBox
+ .querySelector(".blocked-permission-icon[data-permission-id='camera']");
+ ok(!cameraIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown");
+
+ let microphoneIcon = gIdentityHandler._identityBox
+ .querySelector(".blocked-permission-icon[data-permission-id='microphone']");
+ ok(!microphoneIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown");
+
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+
+ ok(!geoIcon.hasAttribute("showing"),
+ "blocked permission icon is not shown after reset");
+});
diff --git a/browser/base/content/test/general/browser_pinnedTabs.js b/browser/base/content/test/general/browser_pinnedTabs.js
new file mode 100644
index 000000000..e0ddb5072
--- /dev/null
+++ b/browser/base/content/test/general/browser_pinnedTabs.js
@@ -0,0 +1,75 @@
+var tabs;
+
+function index(tab) {
+ return Array.indexOf(gBrowser.tabs, tab);
+}
+
+function indexTest(tab, expectedIndex, msg) {
+ var diag = "tab " + tab + " should be at index " + expectedIndex;
+ if (msg)
+ msg = msg + " (" + diag + ")";
+ else
+ msg = diag;
+ is(index(tabs[tab]), expectedIndex, msg);
+}
+
+function PinUnpinHandler(tab, eventName) {
+ this.eventCount = 0;
+ var self = this;
+ tab.addEventListener(eventName, function() {
+ tab.removeEventListener(eventName, arguments.callee, true);
+
+ self.eventCount++;
+ }, true);
+ gBrowser.tabContainer.addEventListener(eventName, function(e) {
+ gBrowser.tabContainer.removeEventListener(eventName, arguments.callee, true);
+
+ if (e.originalTarget == tab) {
+ self.eventCount++;
+ }
+ }, true);
+}
+
+function test() {
+ tabs = [gBrowser.selectedTab, gBrowser.addTab(), gBrowser.addTab(), gBrowser.addTab()];
+ indexTest(0, 0);
+ indexTest(1, 1);
+ indexTest(2, 2);
+ indexTest(3, 3);
+
+ var eh = new PinUnpinHandler(tabs[3], "TabPinned");
+ gBrowser.pinTab(tabs[3]);
+ is(eh.eventCount, 2, "TabPinned event should be fired");
+ indexTest(0, 1);
+ indexTest(1, 2);
+ indexTest(2, 3);
+ indexTest(3, 0);
+
+ eh = new PinUnpinHandler(tabs[1], "TabPinned");
+ gBrowser.pinTab(tabs[1]);
+ is(eh.eventCount, 2, "TabPinned event should be fired");
+ indexTest(0, 2);
+ indexTest(1, 1);
+ indexTest(2, 3);
+ indexTest(3, 0);
+
+ gBrowser.moveTabTo(tabs[3], 3);
+ indexTest(3, 1, "shouldn't be able to mix a pinned tab into normal tabs");
+
+ gBrowser.moveTabTo(tabs[2], 0);
+ indexTest(2, 2, "shouldn't be able to mix a normal tab into pinned tabs");
+
+ eh = new PinUnpinHandler(tabs[1], "TabUnpinned");
+ gBrowser.unpinTab(tabs[1]);
+ is(eh.eventCount, 2, "TabUnpinned event should be fired");
+ indexTest(1, 1, "unpinning a tab should move a tab to the start of normal tabs");
+
+ eh = new PinUnpinHandler(tabs[3], "TabUnpinned");
+ gBrowser.unpinTab(tabs[3]);
+ is(eh.eventCount, 2, "TabUnpinned event should be fired");
+ indexTest(3, 0, "unpinning a tab should move a tab to the start of normal tabs");
+
+ gBrowser.removeTab(tabs[1]);
+ gBrowser.removeTab(tabs[2]);
+ gBrowser.removeTab(tabs[3]);
+}
diff --git a/browser/base/content/test/general/browser_plainTextLinks.js b/browser/base/content/test/general/browser_plainTextLinks.js
new file mode 100644
index 000000000..7a304fce0
--- /dev/null
+++ b/browser/base/content/test/general/browser_plainTextLinks.js
@@ -0,0 +1,146 @@
+function testExpected(expected, msg) {
+ is(document.getElementById("context-openlinkincurrent").hidden, expected, msg);
+}
+
+function testLinkExpected(expected, msg) {
+ is(gContextMenu.linkURL, expected, msg);
+}
+
+add_task(function *() {
+ const url = "data:text/html;charset=UTF-8,Test For Non-Hyperlinked url selection";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW);
+
+ // Initial setup of the content area.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+ let doc = content.document;
+ let range = doc.createRange();
+ let selection = content.getSelection();
+
+ let mainDiv = doc.createElement("div");
+ let div = doc.createElement("div");
+ let div2 = doc.createElement("div");
+ let span1 = doc.createElement("span");
+ let span2 = doc.createElement("span");
+ let span3 = doc.createElement("span");
+ let span4 = doc.createElement("span");
+ let p1 = doc.createElement("p");
+ let p2 = doc.createElement("p");
+ span1.textContent = "http://index.";
+ span2.textContent = "example.com example.com";
+ span3.textContent = " - Test";
+ span4.innerHTML = "<a href='http://www.example.com'>http://www.example.com/example</a>";
+ p1.textContent = "mailto:test.com ftp.example.com";
+ p2.textContent = "example.com -";
+ div.appendChild(span1);
+ div.appendChild(span2);
+ div.appendChild(span3);
+ div.appendChild(span4);
+ div.appendChild(p1);
+ div.appendChild(p2);
+ let p3 = doc.createElement("p");
+ p3.textContent = "main.example.com";
+ div2.appendChild(p3);
+ mainDiv.appendChild(div);
+ mainDiv.appendChild(div2);
+ doc.body.appendChild(mainDiv);
+
+ function setSelection(el1, el2, index1, index2) {
+ while (el1.nodeType != el1.TEXT_NODE)
+ el1 = el1.firstChild;
+ while (el2.nodeType != el1.TEXT_NODE)
+ el2 = el2.firstChild;
+
+ selection.removeAllRanges();
+ range.setStart(el1, index1);
+ range.setEnd(el2, index2);
+ selection.addRange(range);
+
+ return range;
+ }
+
+ // Each of these tests creates a selection and returns a range within it.
+ content.tests = [
+ () => setSelection(span1.firstChild, span2.firstChild, 0, 11),
+ () => setSelection(span1.firstChild, span2.firstChild, 7, 11),
+ () => setSelection(span1.firstChild, span2.firstChild, 8, 11),
+ () => setSelection(span2.firstChild, span2.firstChild, 0, 11),
+ () => setSelection(span2.firstChild, span2.firstChild, 11, 23),
+ () => setSelection(span2.firstChild, span2.firstChild, 0, 10),
+ () => setSelection(span2.firstChild, span3.firstChild, 12, 7),
+ () => setSelection(span2.firstChild, span2.firstChild, 12, 19),
+ () => setSelection(p1.firstChild, p1.firstChild, 0, 15),
+ () => setSelection(p1.firstChild, p1.firstChild, 16, 31),
+ () => setSelection(p2.firstChild, p2.firstChild, 0, 14),
+ () => {
+ selection.selectAllChildren(div2);
+ return selection.getRangeAt(0);
+ },
+ () => {
+ selection.selectAllChildren(span4);
+ return selection.getRangeAt(0);
+ },
+ () => {
+ mainDiv.innerHTML = "(open-suse.ru)";
+ return setSelection(mainDiv, mainDiv, 1, 13);
+ },
+ () => setSelection(mainDiv, mainDiv, 1, 14)
+ ];
+ });
+
+ let checks = [
+ () => testExpected(false, "The link context menu should show for http://www.example.com"),
+ () => testExpected(false, "The link context menu should show for www.example.com"),
+ () => testExpected(true, "The link context menu should not show for ww.example.com"),
+ () => {
+ testExpected(false, "The link context menu should show for example.com");
+ testLinkExpected("http://example.com/", "url for example.com selection should not prepend www");
+ },
+ () => testExpected(false, "The link context menu should show for example.com"),
+ () => testExpected(true, "Link options should not show for selection that's not at a word boundary"),
+ () => testExpected(true, "Link options should not show for selection that has whitespace"),
+ () => testExpected(true, "Link options should not show unless a url is selected"),
+ () => testExpected(true, "Link options should not show for mailto: links"),
+ () => {
+ testExpected(false, "Link options should show for ftp.example.com");
+ testLinkExpected("http://ftp.example.com/", "ftp.example.com should be preceeded with http://");
+ },
+ () => testExpected(false, "Link options should show for www.example.com "),
+ () => testExpected(false, "Link options should show for triple-click selections"),
+ () => testLinkExpected("http://www.example.com/", "Linkified text should open the correct link"),
+ () => {
+ testExpected(false, "Link options should show for open-suse.ru");
+ testLinkExpected("http://open-suse.ru/", "Linkified text should open the correct link");
+ },
+ () => testExpected(true, "Link options should not show for 'open-suse.ru)'")
+ ];
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ for (let testid = 0; testid < checks.length; testid++) {
+ let menuPosition = yield ContentTask.spawn(gBrowser.selectedBrowser, { testid: testid }, function* (arg) {
+ let range = content.tests[arg.testid]();
+
+ // Get the range of the selection and determine its coordinates. These
+ // coordinates will be returned to the parent process and the context menu
+ // will be opened at that location.
+ let rangeRect = range.getBoundingClientRect();
+ return [rangeRect.x + 3, rangeRect.y + 3];
+ });
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtPoint(menuPosition[0], menuPosition[1],
+ { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ checks[testid]();
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_printpreview.js b/browser/base/content/test/general/browser_printpreview.js
new file mode 100644
index 000000000..c38fc18be
--- /dev/null
+++ b/browser/base/content/test/general/browser_printpreview.js
@@ -0,0 +1,74 @@
+let ourTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true).then(function(tab) {
+ ourTab = tab;
+ ok(!gInPrintPreviewMode,
+ "Should NOT be in print preview mode at starting this tests");
+ // Skip access key test on platforms which don't support access key.
+ if (!/Win|Linux/.test(navigator.platform)) {
+ openPrintPreview(testClosePrintPreviewWithEscKey);
+ } else {
+ openPrintPreview(testClosePrintPreviewWithAccessKey);
+ }
+ });
+}
+
+function tidyUp() {
+ BrowserTestUtils.removeTab(ourTab).then(finish);
+}
+
+function testClosePrintPreviewWithAccessKey() {
+ EventUtils.synthesizeKey("c", { altKey: true });
+ checkPrintPreviewClosed(function (aSucceeded) {
+ ok(aSucceeded,
+ "print preview mode should be finished by access key");
+ openPrintPreview(testClosePrintPreviewWithEscKey);
+ });
+}
+
+function testClosePrintPreviewWithEscKey() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ checkPrintPreviewClosed(function (aSucceeded) {
+ ok(aSucceeded,
+ "print preview mode should be finished by Esc key press");
+ openPrintPreview(testClosePrintPreviewWithClosingWindowShortcutKey);
+ });
+}
+
+function testClosePrintPreviewWithClosingWindowShortcutKey() {
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ checkPrintPreviewClosed(function (aSucceeded) {
+ ok(aSucceeded,
+ "print preview mode should be finished by closing window shortcut key");
+ tidyUp();
+ });
+}
+
+function openPrintPreview(aCallback) {
+ document.getElementById("cmd_printPreview").doCommand();
+ executeSoon(function () {
+ if (gInPrintPreviewMode) {
+ executeSoon(aCallback);
+ return;
+ }
+ executeSoon(arguments.callee);
+ });
+}
+
+function checkPrintPreviewClosed(aCallback) {
+ let count = 0;
+ executeSoon(function () {
+ if (!gInPrintPreviewMode) {
+ executeSoon(function () { aCallback(count < 1000); });
+ return;
+ }
+ if (++count == 1000) {
+ // The test might fail.
+ PrintUtils.exitPrintPreview();
+ }
+ executeSoon(arguments.callee);
+ });
+}
diff --git a/browser/base/content/test/general/browser_private_browsing_window.js b/browser/base/content/test/general/browser_private_browsing_window.js
new file mode 100644
index 000000000..607a34060
--- /dev/null
+++ b/browser/base/content/test/general/browser_private_browsing_window.js
@@ -0,0 +1,65 @@
+// Make sure that we can open private browsing windows
+
+function test() {
+ waitForExplicitFinish();
+ var nonPrivateWin = OpenBrowserWindow();
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow() should open a normal window");
+ nonPrivateWin.close();
+
+ var privateWin = OpenBrowserWindow({private: true});
+ ok(PrivateBrowsingUtils.isWindowPrivate(privateWin), "OpenBrowserWindow({private: true}) should open a private window");
+
+ nonPrivateWin = OpenBrowserWindow({private: false});
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow({private: false}) should open a normal window");
+ nonPrivateWin.close();
+
+ whenDelayedStartupFinished(privateWin, function() {
+ nonPrivateWin = privateWin.OpenBrowserWindow({private: false});
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "privateWin.OpenBrowserWindow({private: false}) should open a normal window");
+
+ nonPrivateWin.close();
+
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accesskey: true },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accesskey: false },
+ ].forEach(function(menu) {
+ let newWindow = privateWin.document.getElementById(menu.normal);
+ let newPrivateWindow = privateWin.document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ ok(!newPrivateWindow.hidden, "New Private Window menu item should be hidden");
+ isnot(newWindow.label, newPrivateWindow.label, "New Window's label shouldn't be overwritten");
+ if (menu.accesskey) {
+ isnot(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey shouldn't be overwritten");
+ }
+ isnot(newWindow.command, newPrivateWindow.command, "New Window's command shouldn't be overwritten");
+ }
+ });
+
+ privateWin.close();
+
+ Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true);
+ privateWin = OpenBrowserWindow({private: true});
+ whenDelayedStartupFinished(privateWin, function() {
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accessKey: true },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accessKey: false },
+ ].forEach(function(menu) {
+ let newWindow = privateWin.document.getElementById(menu.normal);
+ let newPrivateWindow = privateWin.document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ ok(newPrivateWindow.hidden, "New Private Window menu item should be hidden");
+ is(newWindow.label, newPrivateWindow.label, "New Window's label should be overwritten");
+ if (menu.accesskey) {
+ is(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey should be overwritten");
+ }
+ is(newWindow.command, newPrivateWindow.command, "New Window's command should be overwritten");
+ }
+ });
+
+ privateWin.close();
+ Services.prefs.clearUserPref("browser.privatebrowsing.autostart");
+ finish();
+ });
+ });
+}
+
diff --git a/browser/base/content/test/general/browser_private_no_prompt.js b/browser/base/content/test/general/browser_private_no_prompt.js
new file mode 100644
index 000000000..c6c580f80
--- /dev/null
+++ b/browser/base/content/test/general/browser_private_no_prompt.js
@@ -0,0 +1,12 @@
+function test() {
+ waitForExplicitFinish();
+ var privateWin = OpenBrowserWindow({private: true});
+
+ whenDelayedStartupFinished(privateWin, function () {
+ privateWin.BrowserOpenTab();
+ privateWin.BrowserTryToCloseWindow();
+ ok(true, "didn't prompt");
+
+ executeSoon(finish);
+ });
+}
diff --git a/browser/base/content/test/general/browser_purgehistory_clears_sh.js b/browser/base/content/test/general/browser_purgehistory_clears_sh.js
new file mode 100644
index 000000000..1a1e6554d
--- /dev/null
+++ b/browser/base/content/test/general/browser_purgehistory_clears_sh.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const url = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+add_task(function* purgeHistoryTest() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url,
+ }, function* purgeHistoryTestInner(browser) {
+ let backButton = browser.ownerDocument.getElementById("Browser:Back");
+ let forwardButton = browser.ownerDocument.getElementById("Browser:Forward");
+
+ ok(!browser.webNavigation.canGoBack,
+ "Initial value for webNavigation.canGoBack");
+ ok(!browser.webNavigation.canGoForward,
+ "Initial value for webNavigation.canGoBack");
+ ok(backButton.hasAttribute("disabled"), "Back button is disabled");
+ ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled");
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let startHistory = content.history.length;
+ content.history.pushState({}, "");
+ content.history.pushState({}, "");
+ content.history.back();
+ let newHistory = content.history.length;
+ Assert.equal(startHistory, 1, "Initial SHistory size");
+ Assert.equal(newHistory, 3, "New SHistory size");
+ });
+
+ ok(browser.webNavigation.canGoBack, true,
+ "New value for webNavigation.canGoBack");
+ ok(browser.webNavigation.canGoForward, true,
+ "New value for webNavigation.canGoForward");
+ ok(!backButton.hasAttribute("disabled"), "Back button was enabled");
+ ok(!forwardButton.hasAttribute("disabled"), "Forward button was enabled");
+
+
+ let tmp = {};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+ let {Sanitizer} = tmp;
+ let sanitizer = new Sanitizer();
+
+ yield sanitizer.sanitize(["history"]);
+
+ yield ContentTask.spawn(browser, null, function*() {
+ Assert.equal(content.history.length, 1, "SHistory correctly cleared");
+ });
+
+ ok(!browser.webNavigation.canGoBack,
+ "webNavigation.canGoBack correctly cleared");
+ ok(!browser.webNavigation.canGoForward,
+ "webNavigation.canGoForward correctly cleared");
+ ok(backButton.hasAttribute("disabled"), "Back button was disabled");
+ ok(forwardButton.hasAttribute("disabled"), "Forward button was disabled");
+ });
+});
diff --git a/browser/base/content/test/general/browser_refreshBlocker.js b/browser/base/content/test/general/browser_refreshBlocker.js
new file mode 100644
index 000000000..ee274f2c2
--- /dev/null
+++ b/browser/base/content/test/general/browser_refreshBlocker.js
@@ -0,0 +1,135 @@
+"use strict";
+
+const META_PAGE = "http://example.org/browser/browser/base/content/test/general/refresh_meta.sjs"
+const HEADER_PAGE = "http://example.org/browser/browser/base/content/test/general/refresh_header.sjs"
+const TARGET_PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+const PREF = "accessibility.blockautorefresh";
+
+/**
+ * Goes into the content, and simulates a meta-refresh header at a very
+ * low level, and checks to see if it was blocked. This will always cancel
+ * the refresh, regardless of whether or not the refresh was blocked.
+ *
+ * @param browser (<xul:browser>)
+ * The browser to test for refreshing.
+ * @param expectRefresh (bool)
+ * Whether or not we expect the refresh attempt to succeed.
+ * @returns Promise
+ */
+function* attemptFakeRefresh(browser, expectRefresh) {
+ yield ContentTask.spawn(browser, expectRefresh, function*(contentExpectRefresh) {
+ let URI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
+ let refresher = docShell.QueryInterface(Ci.nsIRefreshURI);
+ refresher.refreshURI(URI, 0, false, true);
+
+ Assert.equal(refresher.refreshPending, contentExpectRefresh,
+ "Got the right refreshPending state");
+
+ if (refresher.refreshPending) {
+ // Cancel the pending refresh
+ refresher.cancelRefreshURITimers();
+ }
+
+ // The RefreshBlocker will wait until onLocationChange has
+ // been fired before it will show any notifications (see bug
+ // 1246291), so we cause this to occur manually here.
+ content.location = URI.spec + "#foo";
+ });
+}
+
+/**
+ * Tests that we can enable the blocking pref and block a refresh
+ * from occurring while showing a notification bar. Also tests that
+ * when we disable the pref, that refreshes can go through again.
+ */
+add_task(function* test_can_enable_and_block() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: TARGET_PAGE,
+ }, function*(browser) {
+ // By default, we should be able to reload the page.
+ yield attemptFakeRefresh(browser, true);
+
+ yield pushPrefs(["accessibility.blockautorefresh", true]);
+
+ let notificationPromise =
+ BrowserTestUtils.waitForNotificationBar(gBrowser, browser,
+ "refresh-blocked");
+
+ yield attemptFakeRefresh(browser, false);
+
+ yield notificationPromise;
+
+ yield pushPrefs(["accessibility.blockautorefresh", false]);
+
+ // Page reloads should go through again.
+ yield attemptFakeRefresh(browser, true);
+ });
+});
+
+/**
+ * Attempts a "real" refresh by opening a tab, and then sending it to
+ * an SJS page that will attempt to cause a refresh. This will also pass
+ * a delay amount to the SJS page. The refresh should be blocked, and
+ * the notification should be shown. Once shown, the "Allow" button will
+ * be clicked, and the refresh will go through. Finally, the helper will
+ * close the tab and resolve the Promise.
+ *
+ * @param refreshPage (string)
+ * The SJS page to use. Use META_PAGE for the <meta> tag refresh
+ * case. Use HEADER_PAGE for the HTTP header case.
+ * @param delay (int)
+ * The amount, in ms, for the page to wait before attempting the
+ * refresh.
+ *
+ * @returns Promise
+ */
+function* testRealRefresh(refreshPage, delay) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function*(browser) {
+ yield pushPrefs(["accessibility.blockautorefresh", true]);
+
+ browser.loadURI(refreshPage + "?p=" + TARGET_PAGE + "&d=" + delay);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // Once browserLoaded resolves, all nsIWebProgressListener callbacks
+ // should have fired, so the notification should be visible.
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ let notification = notificationBox.currentNotification;
+
+ ok(notification, "Notification should be visible");
+ is(notification.value, "refresh-blocked",
+ "Should be showing the right notification");
+
+ // Then click the button to allow the refresh.
+ let buttons = notification.querySelectorAll(".notification-button");
+ is(buttons.length, 1, "Should have one button.");
+
+ // Prepare a Promise that should resolve when the refresh goes through
+ let refreshPromise = BrowserTestUtils.browserLoaded(browser);
+ buttons[0].click();
+
+ yield refreshPromise;
+ });
+}
+
+/**
+ * Tests the meta-tag case for both short and longer delay times.
+ */
+add_task(function* test_can_allow_refresh() {
+ yield testRealRefresh(META_PAGE, 0);
+ yield testRealRefresh(META_PAGE, 100);
+ yield testRealRefresh(META_PAGE, 500);
+});
+
+/**
+ * Tests that when a HTTP header case for both short and longer
+ * delay times.
+ */
+add_task(function* test_can_block_refresh_from_header() {
+ yield testRealRefresh(HEADER_PAGE, 0);
+ yield testRealRefresh(HEADER_PAGE, 100);
+ yield testRealRefresh(HEADER_PAGE, 500);
+});
diff --git a/browser/base/content/test/general/browser_registerProtocolHandler_notification.html b/browser/base/content/test/general/browser_registerProtocolHandler_notification.html
new file mode 100644
index 000000000..241b03b95
--- /dev/null
+++ b/browser/base/content/test/general/browser_registerProtocolHandler_notification.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Protocol registrar page</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/browser_registerProtocolHandler_notification.js b/browser/base/content/test/general/browser_registerProtocolHandler_notification.js
new file mode 100644
index 000000000..b30ece0f6
--- /dev/null
+++ b/browser/base/content/test/general/browser_registerProtocolHandler_notification.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+ let notificationValue = "Protocol Registration: testprotocol";
+ let testURI = "http://example.com/browser/" +
+ "browser/base/content/test/general/browser_registerProtocolHandler_notification.html";
+
+ waitForCondition(function() {
+ // Do not start until the notification is up
+ let notificationBox = window.gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue(notificationValue);
+ return notification;
+ },
+ function() {
+
+ let notificationBox = window.gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue(notificationValue);
+ ok(notification, "Notification box should be displayed");
+ if (notification == null) {
+ finish();
+ return;
+ }
+ is(notification.type, "info", "We expect this notification to have the type of 'info'.");
+ isnot(notification.image, null, "We expect this notification to have an icon.");
+
+ let buttons = notification.getElementsByClassName("notification-button-default");
+ is(buttons.length, 1, "We expect see one default button.");
+
+ buttons = notification.getElementsByClassName("notification-button");
+ is(buttons.length, 1, "We expect see one button.");
+
+ let button = buttons[0];
+ isnot(button.label, null, "We expect the add button to have a label.");
+ todo_isnot(button.accesskey, null, "We expect the add button to have a accesskey.");
+
+ finish();
+ }, "Still can not get notification after retry 100 times.", 100);
+
+ window.gBrowser.selectedBrowser.loadURI(testURI);
+}
diff --git a/browser/base/content/test/general/browser_relatedTabs.js b/browser/base/content/test/general/browser_relatedTabs.js
new file mode 100644
index 000000000..97cf51d84
--- /dev/null
+++ b/browser/base/content/test/general/browser_relatedTabs.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function*() {
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add several new tabs in sequence, interrupted by selecting a
+ // different tab, moving a tab around and closing a tab,
+ // returning a list of opened tabs for verifying the expected order.
+ // The new tab behaviour is documented in bug 465673
+ let tabs = [];
+ function addTab(aURL, aReferrer) {
+ let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer});
+ tabs.push(tab);
+ return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ }
+
+ yield addTab("http://mochi.test:8888/#0");
+ gBrowser.selectedTab = tabs[0];
+ yield addTab("http://mochi.test:8888/#1");
+ yield addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
+ yield addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
+ gBrowser.selectedTab = tabs[tabs.length - 1];
+ gBrowser.selectedTab = tabs[0];
+ yield addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
+ gBrowser.selectedTab = tabs[3];
+ yield addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
+ gBrowser.removeTab(tabs.pop());
+ yield addTab("about:blank", gBrowser.currentURI);
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+ yield addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
+ yield addTab();
+ yield addTab("http://mochi.test:8888/#7");
+
+ function testPosition(tabNum, expectedPosition, msg) {
+ is(Array.indexOf(gBrowser.tabs, tabs[tabNum]), expectedPosition, msg);
+ }
+
+ testPosition(0, 3, "tab without referrer was opened to the far right");
+ testPosition(1, 7, "tab without referrer was opened to the far right");
+ testPosition(2, 5, "tab with referrer opened immediately to the right");
+ testPosition(3, 1, "next tab with referrer opened further to the right");
+ testPosition(4, 4, "tab selection changed, tab opens immediately to the right");
+ testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was");
+ testPosition(6, 2, "tab has moved, new tab opens immediately to the right");
+ testPosition(7, 8, "blank tab without referrer opens at the end");
+ testPosition(8, 9, "tab without referrer opens at the end");
+
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+});
diff --git a/browser/base/content/test/general/browser_remoteTroubleshoot.js b/browser/base/content/test/general/browser_remoteTroubleshoot.js
new file mode 100644
index 000000000..5c939dbd0
--- /dev/null
+++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
+
+const TEST_URL_TAIL = "example.com/browser/browser/base/content/test/general/test_remoteTroubleshoot.html"
+const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URL_TAIL, null, null);
+const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URL_TAIL, null, null);
+const TEST_URI_GOOD_OBJECT = Services.io.newURI("https://" + TEST_URL_TAIL + "?object", null, null);
+
+// Creates a one-shot web-channel for the test data to be sent back from the test page.
+function promiseChannelResponse(channelID, originOrPermission) {
+ return new Promise((resolve, reject) => {
+ let channel = new WebChannel(channelID, originOrPermission);
+ channel.listen((id, data, target) => {
+ channel.stopListening();
+ resolve(data);
+ });
+ });
+}
+
+// Loads the specified URI in a new tab and waits for it to send us data on our
+// test web-channel and resolves with that data.
+function promiseNewChannelResponse(uri) {
+ let channelPromise = promiseChannelResponse("test-remote-troubleshooting-backchannel",
+ uri);
+ let tab = gBrowser.loadOneTab(uri.spec, { inBackground: false });
+ return promiseTabLoaded(tab).then(
+ () => channelPromise
+ ).then(data => {
+ gBrowser.removeTab(tab);
+ return data;
+ });
+}
+
+add_task(function*() {
+ // We haven't set a permission yet - so even the "good" URI should fail.
+ let got = yield promiseNewChannelResponse(TEST_URI_GOOD);
+ // Should have no data.
+ Assert.ok(got.message === undefined, "should have failed to get any data");
+
+ // Add a permission manager entry for our URI.
+ Services.perms.add(TEST_URI_GOOD,
+ "remote-troubleshooting",
+ Services.perms.ALLOW_ACTION);
+ registerCleanupFunction(() => {
+ Services.perms.remove(TEST_URI_GOOD, "remote-troubleshooting");
+ });
+
+ // Try again - now we are expecting a response with the actual data.
+ got = yield promiseNewChannelResponse(TEST_URI_GOOD);
+
+ // Check some keys we expect to always get.
+ Assert.ok(got.message.extensions, "should have extensions");
+ Assert.ok(got.message.graphics, "should have graphics");
+
+ // Check we have channel and build ID info:
+ Assert.equal(got.message.application.buildID, Services.appinfo.appBuildID,
+ "should have correct build ID");
+
+ let updateChannel = null;
+ try {
+ updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
+ } catch (ex) {}
+ if (!updateChannel) {
+ Assert.ok(!('updateChannel' in got.message.application),
+ "should not have update channel where not available.");
+ } else {
+ Assert.equal(got.message.application.updateChannel, updateChannel,
+ "should have correct update channel.");
+ }
+
+
+ // And check some keys we know we decline to return.
+ Assert.ok(!got.message.modifiedPreferences, "should not have a modifiedPreferences key");
+ Assert.ok(!got.message.crashes, "should not have crash info");
+
+ // Now a http:// URI - should get nothing even with the permission setup.
+ got = yield promiseNewChannelResponse(TEST_URI_BAD);
+ Assert.ok(got.message === undefined, "should have failed to get any data");
+
+ // Check that the page can send an object as well if it's in the whitelist
+ let webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " https://example.com";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
+ });
+ got = yield promiseNewChannelResponse(TEST_URI_GOOD_OBJECT);
+ Assert.ok(got.message, "should have gotten some data back");
+});
diff --git a/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js
new file mode 100644
index 000000000..451323f50
--- /dev/null
+++ b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+function makeInputStream(aString) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.data = aString;
+ return stream; // XPConnect will QI this to nsIInputStream for us.
+}
+
+add_task(function* test_remoteWebNavigation_postdata() {
+ let obj = {};
+ Cu.import("resource://testing-common/httpd.js", obj);
+ Cu.import("resource://services-common/utils.js", obj);
+
+ let server = new obj.HttpServer();
+ server.start(-1);
+
+ let loadDeferred = Promise.defer();
+
+ server.registerPathHandler("/test", (request, response) => {
+ let body = obj.CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+ is(body, "success", "request body is correct");
+ is(request.method, "POST", "request was a post");
+ response.write("Received from POST: " + body);
+ loadDeferred.resolve();
+ });
+
+ let i = server.identity;
+ let path = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/test";
+
+ let postdata =
+ "Content-Length: 7\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "\r\n" +
+ "success";
+
+ openUILinkIn(path, "tab", null, makeInputStream(postdata));
+
+ yield loadDeferred.promise;
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ let serverStoppedDeferred = Promise.defer();
+ server.stop(function() { serverStoppedDeferred.resolve(); });
+ yield serverStoppedDeferred.promise;
+});
diff --git a/browser/base/content/test/general/browser_removeTabsToTheEnd.js b/browser/base/content/test/general/browser_removeTabsToTheEnd.js
new file mode 100644
index 000000000..351085d74
--- /dev/null
+++ b/browser/base/content/test/general/browser_removeTabsToTheEnd.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Add two new tabs after the original tab. Pin the first one.
+ let originalTab = gBrowser.selectedTab;
+ let newTab1 = gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.pinTab(newTab1);
+
+ // Check that there is only one closable tab from originalTab to the end
+ is(gBrowser.getTabsToTheEndFrom(originalTab).length, 1,
+ "One unpinned tab to the right");
+
+ // Remove tabs to the end
+ gBrowser.removeTabsToTheEndFrom(originalTab);
+ is(gBrowser.tabs.length, 2, "Length is 2");
+ is(gBrowser.tabs[1], originalTab, "Starting tab is not removed");
+ is(gBrowser.tabs[0], newTab1, "Pinned tab is not removed");
+
+ // Remove pinned tab
+ gBrowser.removeTab(newTab1);
+}
diff --git a/browser/base/content/test/general/browser_restore_isAppTab.js b/browser/base/content/test/general/browser_restore_isAppTab.js
new file mode 100644
index 000000000..e20974d80
--- /dev/null
+++ b/browser/base/content/test/general/browser_restore_isAppTab.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+const DUMMY = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+function getMinidumpDirectory() {
+ let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ dir.append("minidumps");
+ return dir;
+}
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+var CrashObserver = {
+ observe: function(subject, topic, data) {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Ci.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ info("This is a normal termination and isn't the one we are looking for...");
+ return;
+ }
+
+ let dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+ }
+
+ if (dumpID) {
+ let minidumpDirectory = getMinidumpDirectory();
+ let file = minidumpDirectory.clone();
+ file.append(dumpID + '.dmp');
+ file.remove(true);
+ file = minidumpDirectory.clone();
+ file.append(dumpID + '.extra');
+ file.remove(true);
+ }
+ }
+}
+Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown');
+});
+
+function frameScript() {
+ addMessageListener("Test:GetIsAppTab", function() {
+ sendAsyncMessage("Test:IsAppTab", { isAppTab: docShell.isAppTab });
+ });
+
+ addMessageListener("Test:Crash", function() {
+ privateNoteIntentionalCrash();
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+ });
+}
+
+function loadFrameScript(browser) {
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+}
+
+function isBrowserAppTab(browser) {
+ return new Promise(resolve => {
+ function listener({ data }) {
+ browser.messageManager.removeMessageListener("Test:IsAppTab", listener);
+ resolve(data.isAppTab);
+ }
+ // It looks like same-process messages may be reordered by the message
+ // manager, so we need to wait one tick before sending the message.
+ executeSoon(function () {
+ browser.messageManager.addMessageListener("Test:IsAppTab", listener);
+ browser.messageManager.sendAsyncMessage("Test:GetIsAppTab");
+ });
+ });
+}
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = Task.async(function*(browser) {
+ // If the tab isn't remote this would crash the main process so skip it
+ if (!browser.isRemoteBrowser)
+ return;
+
+ // Make sure the main process has all of the current tab state before crashing
+ yield TabStateFlusher.flush(browser);
+
+ browser.messageManager.sendAsyncMessage("Test:Crash");
+ yield promiseWaitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ SessionStore.reviveCrashedTab(tab);
+
+ yield promiseTabLoaded(tab);
+});
+
+add_task(function* navigate() {
+ let tab = gBrowser.addTab("about:robots");
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ let isAppTab = yield isBrowserAppTab(browser);
+ ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+ gBrowser.pinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.loadURI(DUMMY);
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.unpinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+ gBrowser.pinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.loadURI("about:robots");
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* crash() {
+ if (!gMultiProcessBrowser || !("nsICrashReporter" in Ci))
+ return;
+
+ let tab = gBrowser.addTab(DUMMY);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ let isAppTab = yield isBrowserAppTab(browser);
+ ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+ gBrowser.pinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ yield restart(browser);
+ loadFrameScript(browser);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
new file mode 100644
index 000000000..4f4f5c398
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
@@ -0,0 +1,39 @@
+// Bug 474792 - Clear "Never remember passwords for this site" when
+// clearing site-specific settings in Clear Recent History dialog
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+add_task(function*() {
+ var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
+
+ // Add a disabled host
+ pwmgr.setLoginSavingEnabled("http://example.com", false);
+ // Sanity check
+ is(pwmgr.getLoginSavingEnabled("http://example.com"), false,
+ "example.com should be disabled for password saving since we haven't cleared that yet.");
+
+ // Set up the sanitizer to just clear siteSettings
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", true);
+
+ // Clear it
+ yield s.sanitize();
+
+ // Make sure it's gone
+ is(pwmgr.getLoginSavingEnabled("http://example.com"), true,
+ "example.com should be enabled for password saving again now that we've cleared.");
+});
diff --git a/browser/base/content/test/general/browser_sanitize-sitepermissions.js b/browser/base/content/test/general/browser_sanitize-sitepermissions.js
new file mode 100644
index 000000000..1b43d62fc
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitize-sitepermissions.js
@@ -0,0 +1,52 @@
+// Bug 380852 - Delete permission manager entries in Clear Recent History
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+function countPermissions() {
+ let result = 0;
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ result++;
+ enumerator.getNext();
+ }
+ return result;
+}
+
+add_task(function* test() {
+ // sanitize before we start so we have a good baseline.
+ // Set up the sanitizer to just clear siteSettings
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", true);
+ s.sanitize();
+
+ // Count how many permissions we start with - some are defaults that
+ // will not be sanitized.
+ let numAtStart = countPermissions();
+
+ // Add a permission entry
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com"), "testing", pm.ALLOW_ACTION);
+
+ // Sanity check
+ ok(pm.enumerator.hasMoreElements(), "Permission manager should have elements, since we just added one");
+
+ // Clear it
+ yield s.sanitize();
+
+ // Make sure it's gone
+ is(numAtStart, countPermissions(), "Permission manager should have the same count it started with");
+});
diff --git a/browser/base/content/test/general/browser_sanitize-timespans.js b/browser/base/content/test/general/browser_sanitize-timespans.js
new file mode 100644
index 000000000..3712c5e1c
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitize-timespans.js
@@ -0,0 +1,733 @@
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+requestLongerTimeout(2);
+
+// Bug 453440 - Test the timespan-based logic of the sanitizer code
+var now_mSec = Date.now();
+var now_uSec = now_mSec * 1000;
+
+const kMsecPerMin = 60 * 1000;
+const kUsecPerMin = 60 * 1000000;
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+var FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
+var Downloads = (Components.utils.import("resource://gre/modules/Downloads.jsm", {})).Downloads;
+
+function promiseFormHistoryRemoved() {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onfh() {
+ Services.obs.removeObserver(onfh, "satchel-storage-changed", false);
+ deferred.resolve();
+ }, "satchel-storage-changed", false);
+ return deferred.promise;
+}
+
+function promiseDownloadRemoved(list) {
+ let deferred = Promise.defer();
+
+ let view = {
+ onDownloadRemoved: function(download) {
+ list.removeView(view);
+ deferred.resolve();
+ }
+ };
+
+ list.addView(view);
+
+ return deferred.promise;
+}
+
+add_task(function* test() {
+ yield setupDownloads();
+ yield setupFormHistory();
+ yield setupHistory();
+ yield onHistoryReady();
+});
+
+function countEntries(name, message, check) {
+ let deferred = Promise.defer();
+
+ var obj = {};
+ if (name !== null)
+ obj.fieldname = name;
+
+ let count;
+ FormHistory.count(obj, { handleResult: result => count = result,
+ handleError: function (error) {
+ deferred.reject(error)
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) {
+ if (!reason) {
+ check(count, message);
+ deferred.resolve();
+ }
+ },
+ });
+
+ return deferred.promise;
+}
+
+function* onHistoryReady() {
+ var hoursSinceMidnight = new Date().getHours();
+ var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes();
+
+ // Should test cookies here, but nsICookieManager/nsICookieService
+ // doesn't let us fake creation times. bug 463127
+
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", true);
+ itemPrefs.setBoolPref("downloads", true);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", true);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let downloadPromise = promiseDownloadRemoved(publicList);
+ let formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 10 minutes ago
+ s.range = [now_uSec - 10*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))),
+ "Pretend visit to 10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+ "Pretend visit to 1hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 10) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ let checkZero = function(num, message) { is(num, 0, message); }
+ let checkOne = function(num, message) { is(num, 1, message); }
+
+ yield countEntries("10minutes", "10minutes form entry should be deleted", checkZero);
+ yield countEntries("1hour", "1hour form entry should still exist", checkOne);
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 10)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-10-minutes")), "10 minute download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+
+ if (minutesSinceMidnight > 10)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 1 hour
+ Sanitizer.prefs.setIntPref("timeSpan", 1);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+ "Pretend visit to 1hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 1) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("1hour", "1hour form entry should be deleted", checkZero);
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 1)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+
+ if (hoursSinceMidnight > 1)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 1 hour 10 minutes
+ s.range = [now_uSec - 70*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 70) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should be deleted", checkZero);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 70)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ if (minutesSinceMidnight > 70)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 2 hours
+ Sanitizer.prefs.setIntPref("timeSpan", 2);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 2) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("2hour", "2hour form entry should be deleted", checkZero);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 2)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ if (hoursSinceMidnight > 2)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 2 hours 10 minutes
+ s.range = [now_uSec - 130*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 130) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should be deleted", checkZero);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 130)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ if (minutesSinceMidnight > 130)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 4 hours
+ Sanitizer.prefs.setIntPref("timeSpan", 3);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 4) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("4hour", "4hour form entry should be deleted", checkZero);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 4)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ if (hoursSinceMidnight > 4)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 4 hours 10 minutes
+ s.range = [now_uSec - 250*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should now be deleted");
+ if (minutesSinceMidnight > 250) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should be deleted", checkZero);
+ if (minutesSinceMidnight > 250)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ if (minutesSinceMidnight > 250)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ // The 'Today' download might have been already deleted, in which case we
+ // should not wait for a download removal notification.
+ if (minutesSinceMidnight > 250) {
+ downloadPromise = promiseDownloadRemoved(publicList);
+ } else {
+ downloadPromise = Promise.resolve();
+ }
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear Today
+ Sanitizer.prefs.setIntPref("timeSpan", 4);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ // Be careful. If we add our objectss just before midnight, and sanitize
+ // runs immediately after, they won't be expired. This is expected, but
+ // we should not test in that case. We cannot just test for opposite
+ // condition because we could cross midnight just one moment after we
+ // cache our time, then we would have an even worse random failure.
+ var today = isToday(new Date(now_mSec));
+ if (today) {
+ ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should now be deleted");
+
+ yield countEntries("today", "today form entry should be deleted", checkZero);
+ ok(!(yield downloadExists(publicList, "fakefile-today")), "'Today' download should now be deleted");
+ }
+
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Choose everything
+ Sanitizer.prefs.setIntPref("timeSpan", 0);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should now be deleted");
+
+ yield countEntries("b4today", "b4today form entry should be deleted", checkZero);
+
+ ok(!(yield downloadExists(publicList, "fakefile-old")), "Year old download should now be deleted");
+}
+
+function setupHistory() {
+ let deferred = Promise.defer();
+
+ let places = [];
+
+ function addPlace(aURI, aTitle, aVisitDate) {
+ places.push({
+ uri: aURI,
+ title: aTitle,
+ visits: [{
+ visitDate: aVisitDate,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ }]
+ });
+ }
+
+ addPlace(makeURI("http://10minutes.com/"), "10 minutes ago", now_uSec - 10 * kUsecPerMin);
+ addPlace(makeURI("http://1hour.com/"), "Less than 1 hour ago", now_uSec - 45 * kUsecPerMin);
+ addPlace(makeURI("http://1hour10minutes.com/"), "1 hour 10 minutes ago", now_uSec - 70 * kUsecPerMin);
+ addPlace(makeURI("http://2hour.com/"), "Less than 2 hours ago", now_uSec - 90 * kUsecPerMin);
+ addPlace(makeURI("http://2hour10minutes.com/"), "2 hours 10 minutes ago", now_uSec - 130 * kUsecPerMin);
+ addPlace(makeURI("http://4hour.com/"), "Less than 4 hours ago", now_uSec - 180 * kUsecPerMin);
+ addPlace(makeURI("http://4hour10minutes.com/"), "4 hours 10 minutesago", now_uSec - 250 * kUsecPerMin);
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+ addPlace(makeURI("http://today.com/"), "Today", today.getTime() * 1000);
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ addPlace(makeURI("http://before-today.com/"), "Before Today", lastYear.getTime() * 1000);
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ handleError: () => ok(false, "Unexpected error in adding visit."),
+ handleResult: () => { },
+ handleCompletion: () => deferred.resolve()
+ });
+
+ return deferred.promise;
+}
+
+function* setupFormHistory() {
+
+ function searchEntries(terms, params) {
+ let deferred = Promise.defer();
+
+ let results = [];
+ FormHistory.search(terms, params, { handleResult: result => results.push(result),
+ handleError: function (error) {
+ deferred.reject(error);
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) { deferred.resolve(results); }
+ });
+ return deferred.promise;
+ }
+
+ function update(changes)
+ {
+ let deferred = Promise.defer();
+ FormHistory.update(changes, { handleError: function (error) {
+ deferred.reject(error);
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) { deferred.resolve(); }
+ });
+ return deferred.promise;
+ }
+
+ // Make sure we've got a clean DB to start with, then add the entries we'll be testing.
+ yield update(
+ [{
+ op: "remove"
+ },
+ {
+ op : "add",
+ fieldname : "10minutes",
+ value : "10m"
+ }, {
+ op : "add",
+ fieldname : "1hour",
+ value : "1h"
+ }, {
+ op : "add",
+ fieldname : "1hour10minutes",
+ value : "1h10m"
+ }, {
+ op : "add",
+ fieldname : "2hour",
+ value : "2h"
+ }, {
+ op : "add",
+ fieldname : "2hour10minutes",
+ value : "2h10m"
+ }, {
+ op : "add",
+ fieldname : "4hour",
+ value : "4h"
+ }, {
+ op : "add",
+ fieldname : "4hour10minutes",
+ value : "4h10m"
+ }, {
+ op : "add",
+ fieldname : "today",
+ value : "1d"
+ }, {
+ op : "add",
+ fieldname : "b4today",
+ value : "1y"
+ }]);
+
+ // Artifically age the entries to the proper vintage.
+ let timestamp = now_uSec - 10 * kUsecPerMin;
+ let results = yield searchEntries(["guid"], { fieldname: "10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 45 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "1hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 70 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "1hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 90 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "2hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 130 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "2hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 180 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "4hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 250 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "4hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+ timestamp = today.getTime() * 1000;
+ results = yield searchEntries(["guid"], { fieldname: "today" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ timestamp = lastYear.getTime() * 1000;
+ results = yield searchEntries(["guid"], { fieldname: "b4today" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ var checks = 0;
+ let checkOne = function(num, message) { is(num, 1, message); checks++; }
+
+ // Sanity check.
+ yield countEntries("10minutes", "Checking for 10minutes form history entry creation", checkOne);
+ yield countEntries("1hour", "Checking for 1hour form history entry creation", checkOne);
+ yield countEntries("1hour10minutes", "Checking for 1hour10minutes form history entry creation", checkOne);
+ yield countEntries("2hour", "Checking for 2hour form history entry creation", checkOne);
+ yield countEntries("2hour10minutes", "Checking for 2hour10minutes form history entry creation", checkOne);
+ yield countEntries("4hour", "Checking for 4hour form history entry creation", checkOne);
+ yield countEntries("4hour10minutes", "Checking for 4hour10minutes form history entry creation", checkOne);
+ yield countEntries("today", "Checking for today form history entry creation", checkOne);
+ yield countEntries("b4today", "Checking for b4today form history entry creation", checkOne);
+ is(checks, 9, "9 checks made");
+}
+
+function* setupDownloads() {
+
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+
+ let download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 10 * kMsecPerMin), // 10 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-1-hour"
+ });
+ download.startTime = new Date(now_mSec - 45 * kMsecPerMin), // 45 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-1-hour-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 70 * kMsecPerMin), // 70 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-2-hour"
+ });
+ download.startTime = new Date(now_mSec - 90 * kMsecPerMin), // 90 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-2-hour-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 130 * kMsecPerMin), // 130 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-4-hour"
+ });
+ download.startTime = new Date(now_mSec - 180 * kMsecPerMin), // 180 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-4-hour-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 250 * kMsecPerMin), // 250 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ // Add "today" download
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-today"
+ });
+ download.startTime = today, // 12:00:01 AM this morning
+ download.canceled = true;
+ yield publicList.add(download);
+
+ // Add "before today" download
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-old"
+ });
+ download.startTime = lastYear,
+ download.canceled = true;
+ yield publicList.add(download);
+
+ // Confirm everything worked
+ let downloads = yield publicList.getAll();
+ is(downloads.length, 9, "9 Pretend downloads added");
+
+ ok((yield downloadExists(publicList, "fakefile-old")), "Pretend download for everything case should exist");
+ ok((yield downloadExists(publicList, "fakefile-10-minutes")), "Pretend download for 10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-1-hour")), "Pretend download for 1-hour case should exist");
+ ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "Pretend download for 1-hour-10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "Pretend download for 2-hour case should exist");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "Pretend download for 2-hour-10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "Pretend download for 4-hour case should exist");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "Pretend download for 4-hour-10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-today")), "Pretend download for Today case should exist");
+}
+
+/**
+ * Checks to see if the downloads with the specified id exists.
+ *
+ * @param aID
+ * The ids of the downloads to check.
+ */
+let downloadExists = Task.async(function* (list, path) {
+ let listArray = yield list.getAll();
+ return listArray.some(i => i.target.path == path);
+});
+
+function isToday(aDate) {
+ return aDate.getDate() == new Date().getDate();
+}
diff --git a/browser/base/content/test/general/browser_sanitizeDialog.js b/browser/base/content/test/general/browser_sanitizeDialog.js
new file mode 100644
index 000000000..50546be45
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -0,0 +1,1027 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
+ * See bug 480169.
+ *
+ * The purpose of this test is not to fully flex the sanitize timespan code;
+ * browser/base/content/test/general/browser_sanitize-timespans.js does that. This
+ * test checks the UI of the dialog and makes sure it's correctly connected to
+ * the sanitize timespan code.
+ *
+ * Some of this code, especially the history creation parts, was taken from
+ * browser/base/content/test/general/browser_sanitize-timespans.js.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Timer",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var tempScope = {};
+Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+const kMsecPerMin = 60 * 1000;
+const kUsecPerMin = 60 * 1000000;
+
+add_task(function* init() {
+ requestLongerTimeout(3);
+ yield blankSlate();
+ registerCleanupFunction(function* () {
+ yield blankSlate();
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ });
+});
+
+/**
+ * Initializes the dialog to its default state.
+ */
+add_task(function* default_state() {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Select "Last Hour"
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ // Hide details
+ if (!this.getItemList().collapsed)
+ this.toggleDetails();
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Cancels the dialog, makes sure history not cleared.
+ */
+add_task(function* test_cancel() {
+ // Add history (within the past hour)
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+ yield PlacesTestUtils.addVisits(places);
+
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", false);
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.cancelDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseHistoryClearedState(uris, false);
+ yield blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the combined history-downloads checkbox clears both history
+ * visits and downloads when checked; the dialog respects simple timespan.
+ */
+add_task(function* test_history_downloads_checked() {
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ yield addDownloadWithMinutesAgo(downloadIDs, i);
+ }
+ // Add downloads (over an hour ago).
+ let olderDownloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
+ }
+
+ // Add history (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+ // Add history (over an hour ago).
+ let olderURIs = [];
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
+ olderURIs.push(pURI);
+ }
+ let promiseSanitized = promiseSanitizationComplete();
+
+ yield PlacesTestUtils.addVisits(places);
+
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected");
+ boolPrefIs("cpd.history", true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ boolPrefIs("cpd.downloads", true,
+ "downloads pref should be true after accepting dialog with " +
+ "history checkbox checked");
+
+ yield promiseSanitized;
+
+ // History visits and downloads within one hour should be cleared.
+ yield promiseHistoryClearedState(uris, true);
+ yield ensureDownloadsClearedState(downloadIDs, true);
+
+ // Visits and downloads > 1 hour should still exist.
+ yield promiseHistoryClearedState(olderURIs, false);
+ yield ensureDownloadsClearedState(olderDownloadIDs, false);
+
+ // OK, done, cleanup after ourselves.
+ yield blankSlate();
+ yield promiseHistoryClearedState(olderURIs, true);
+ yield ensureDownloadsClearedState(olderDownloadIDs, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the combined history-downloads checkbox removes neither
+ * history visits nor downloads when not checked.
+ */
+add_task(function* test_history_downloads_unchecked() {
+ // Add form entries
+ let formEntries = [];
+
+ for (let i = 0; i < 5; i++) {
+ formEntries.push((yield promiseAddFormEntryWithMinutesAgo(i)));
+ }
+
+
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ yield addDownloadWithMinutesAgo(downloadIDs, i);
+ }
+
+ // Add history, downloads, form entries (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+
+ yield PlacesTestUtils.addVisits(places);
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan");
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+
+ // Remove only form entries, leave history (including downloads).
+ this.checkPrefCheckbox("history", false);
+ this.checkPrefCheckbox("formdata", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected");
+ boolPrefIs("cpd.history", false,
+ "history pref should be false after accepting dialog with " +
+ "history checkbox unchecked");
+ boolPrefIs("cpd.downloads", false,
+ "downloads pref should be false after accepting dialog with " +
+ "history checkbox unchecked");
+
+ // Of the three only form entries should be cleared.
+ yield promiseHistoryClearedState(uris, false);
+ yield ensureDownloadsClearedState(downloadIDs, false);
+
+ for (let entry of formEntries) {
+ let exists = yield formNameExists(entry);
+ is(exists, false, "form entry " + entry + " should no longer exist");
+ }
+
+ // OK, done, cleanup after ourselves.
+ yield blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ yield ensureDownloadsClearedState(downloadIDs, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the "Everything" duration option works.
+ */
+add_task(function* test_everything() {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ yield PlacesTestUtils.addVisits(places);
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan");
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected");
+
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the "Everything" warning is visible on dialog open after
+ * the previous test.
+ */
+add_task(function* test_everything_warning() {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ yield PlacesTestUtils.addVisits(places);
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible after previously accepting dialog " +
+ "with clearing everything");
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected");
+
+ yield promiseSanitized;
+
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * The next three tests checks that when a certain history item cannot be
+ * cleared then the checkbox should be both disabled and unchecked.
+ * In addition, we ensure that this behavior does not modify the preferences.
+ */
+add_task(function* test_cannot_clear_history() {
+ // Add form entries
+ let formEntries = [ (yield promiseAddFormEntryWithMinutesAgo(10)) ];
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ // Add history.
+ let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
+ yield PlacesTestUtils.addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)});
+ let uris = [ pURI ];
+
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ // Check that the relevant checkboxes are enabled
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+ ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " +
+ "clear formdata should be enabled.");
+
+ cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.history']");
+ ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
+ "clear history should be enabled.");
+
+ this.checkAllCheckboxes();
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+
+ yield promiseHistoryClearedState(uris, true);
+
+ let exists = yield formNameExists(formEntries[0]);
+ is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+add_task(function* test_no_formdata_history_to_clear() {
+ let promiseSanitized = promiseSanitizationComplete();
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ boolPrefIs("cpd.history", true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ boolPrefIs("cpd.formdata", true,
+ "formdata pref should be true after accepting dialog with " +
+ "formdata checkbox checked");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.history']");
+ ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+ "There is no history, but history checkbox should always be enabled " +
+ "and will be checked from previous preference.");
+
+ this.acceptDialog();
+ }
+ wh.open();
+ yield wh.promiseClosed;
+ yield promiseSanitized;
+});
+
+add_task(function* test_form_entries() {
+ let formEntry = (yield promiseAddFormEntryWithMinutesAgo(10));
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ boolPrefIs("cpd.formdata", true,
+ "formdata pref should persist previous value after accepting " +
+ "dialog where you could not clear formdata.");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+
+ info("There exists formEntries so the checkbox should be in sync with the pref.");
+ is(cb.length, 1, "There is only one checkbox for form data");
+ ok(!cb[0].disabled, "The checkbox is enabled");
+ ok(cb[0].checked, "The checkbox is checked");
+
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+ let exists = yield formNameExists(formEntry);
+ is(exists, false, "form entry " + formEntry + " should no longer exist");
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+
+/**
+ * Ensure that toggling details persists
+ * across dialog openings.
+ */
+add_task(function* test_toggling_details_persists() {
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Check all items and select "Everything"
+ this.checkAllCheckboxes();
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should remain closed because all items are checked.
+ this.checkDetails(false);
+
+ // Uncheck history.
+ this.checkPrefCheckbox("history", false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Modify the Site Preferences item state (bug 527820)
+ this.checkAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.cancelDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Select another duration
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should not be open because "Last Hour" is selected
+ this.checkDetails(false);
+
+ this.cancelDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should have remained closed
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+ this.cancelDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+});
+
+// Test for offline cache deletion
+add_task(function* test_offline_cache() {
+ // Prepare stuff, we will work with www.example.com
+ var URL = "http://www.example.com";
+ var URI = makeURI(URL);
+ var principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI);
+
+ // Give www.example.com privileges to store offline data
+ Services.perms.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+ Services.perms.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+
+ // Store something to the offline cache
+ var appcacheserv = Cc["@mozilla.org/network/application-cache-service;1"]
+ .getService(Ci.nsIApplicationCacheService);
+ var appcachegroupid = appcacheserv.buildGroupIDForInfo(makeURI(URL + "/manifest"), LoadContextInfo.default);
+ var appcache = appcacheserv.createApplicationCache(appcachegroupid);
+ var storage = Services.cache2.appCacheStorage(LoadContextInfo.default, appcache);
+
+ // Open the dialog
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Show details
+ this.toggleDetails();
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("offlineApps", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function () {
+ // Check if the cache has been deleted
+ var size = -1;
+ var visitor = {
+ onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
+ {
+ size = aConsumption;
+ }
+ };
+ storage.asyncVisitStorage(visitor, false);
+ // Offline cache visit happens synchronously, since it's forwarded to the old code
+ is(size, 0, "offline application cache entries evicted");
+ };
+
+ var cacheListener = {
+ onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
+ onCacheEntryAvailable: function (entry, isnew, unused, status) {
+ is(status, Cr.NS_OK);
+ var stream = entry.openOutputStream(0);
+ var content = "content";
+ stream.write(content, content.length);
+ stream.close();
+ entry.close();
+ wh.open();
+ }
+ };
+
+ storage.asyncOpenURI(makeURI(URL), "", Ci.nsICacheStorage.OPEN_TRUNCATE, cacheListener);
+ yield wh.promiseClosed;
+});
+
+// Test for offline apps permission deletion
+add_task(function* test_offline_apps_permissions() {
+ // Prepare stuff, we will work with www.example.com
+ var URL = "http://www.example.com";
+ var URI = makeURI(URL);
+ var principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {});
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ // Open the dialog
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Show details
+ this.toggleDetails();
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+
+ // Check all has been deleted (privileges, data, cache)
+ is(Services.perms.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed");
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+var now_mSec = Date.now();
+var now_uSec = now_mSec * 1000;
+
+/**
+ * This wraps the dialog and provides some convenience methods for interacting
+ * with it.
+ *
+ * @param aWin
+ * The dialog's nsIDOMWindow
+ */
+function WindowHelper(aWin) {
+ this.win = aWin;
+ this.promiseClosed = new Promise(resolve => { this._resolveClosed = resolve });
+}
+
+WindowHelper.prototype = {
+ /**
+ * "Presses" the dialog's OK button.
+ */
+ acceptDialog: function () {
+ is(this.win.document.documentElement.getButton("accept").disabled, false,
+ "Dialog's OK button should not be disabled");
+ this.win.document.documentElement.acceptDialog();
+ },
+
+ /**
+ * "Presses" the dialog's Cancel button.
+ */
+ cancelDialog: function () {
+ this.win.document.documentElement.cancelDialog();
+ },
+
+ /**
+ * Ensures that the details progressive disclosure button and the item list
+ * hidden by it match up. Also makes sure the height of the dialog is
+ * sufficient for the item list and warning panel.
+ *
+ * @param aShouldBeShown
+ * True if you expect the details to be shown and false if hidden
+ */
+ checkDetails: function (aShouldBeShown) {
+ let button = this.getDetailsButton();
+ let list = this.getItemList();
+ let hidden = list.hidden || list.collapsed;
+ is(hidden, !aShouldBeShown,
+ "Details should be " + (aShouldBeShown ? "shown" : "hidden") +
+ " but were actually " + (hidden ? "hidden" : "shown"));
+ let dir = hidden ? "down" : "up";
+ is(button.className, "expander-" + dir,
+ "Details button should be " + dir + " because item list is " +
+ (hidden ? "" : "not ") + "hidden");
+ let height = 0;
+ if (!hidden) {
+ ok(list.boxObject.height > 30, "listbox has sufficient size")
+ height += list.boxObject.height;
+ }
+ if (this.isWarningPanelVisible())
+ height += this.getWarningPanel().boxObject.height;
+ ok(height < this.win.innerHeight,
+ "Window should be tall enough to fit warning panel and item list");
+ },
+
+ /**
+ * (Un)checks a history scope checkbox (browser & download history,
+ * form history, etc.).
+ *
+ * @param aPrefName
+ * The final portion of the checkbox's privacy.cpd.* preference name
+ * @param aCheckState
+ * True if the checkbox should be checked, false otherwise
+ */
+ checkPrefCheckbox: function (aPrefName, aCheckState) {
+ var pref = "privacy.cpd." + aPrefName;
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='" + pref + "']");
+ is(cb.length, 1, "found checkbox for " + pref + " preference");
+ if (cb[0].checked != aCheckState)
+ cb[0].click();
+ },
+
+ /**
+ * Makes sure all the checkboxes are checked.
+ */
+ _checkAllCheckboxesCustom: function (check) {
+ var cb = this.win.document.querySelectorAll("#itemList > [preference]");
+ ok(cb.length > 1, "found checkboxes for preferences");
+ for (var i = 0; i < cb.length; ++i) {
+ var pref = this.win.document.getElementById(cb[i].getAttribute("preference"));
+ if (!!pref.value ^ check)
+ cb[i].click();
+ }
+ },
+
+ checkAllCheckboxes: function () {
+ this._checkAllCheckboxesCustom(true);
+ },
+
+ uncheckAllCheckboxes: function () {
+ this._checkAllCheckboxesCustom(false);
+ },
+
+ /**
+ * @return The details progressive disclosure button
+ */
+ getDetailsButton: function () {
+ return this.win.document.getElementById("detailsExpander");
+ },
+
+ /**
+ * @return The dialog's duration dropdown
+ */
+ getDurationDropdown: function () {
+ return this.win.document.getElementById("sanitizeDurationChoice");
+ },
+
+ /**
+ * @return The item list hidden by the details progressive disclosure button
+ */
+ getItemList: function () {
+ return this.win.document.getElementById("itemList");
+ },
+
+ /**
+ * @return The clear-everything warning box
+ */
+ getWarningPanel: function () {
+ return this.win.document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ /**
+ * @return True if the "Everything" warning panel is visible (as opposed to
+ * the tree)
+ */
+ isWarningPanelVisible: function () {
+ return !this.getWarningPanel().hidden;
+ },
+
+ /**
+ * Opens the clear recent history dialog. Before calling this, set
+ * this.onload to a function to execute onload. It should close the dialog
+ * when done so that the tests may continue. Set this.onunload to a function
+ * to execute onunload. this.onunload is optional. If it returns true, the
+ * caller is expected to call waitForAsyncUpdates at some point; if false is
+ * returned, waitForAsyncUpdates is called automatically.
+ */
+ open: function () {
+ let wh = this;
+
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened")
+ return;
+
+ Services.ww.unregisterNotification(windowObserver);
+
+ var loaded = false;
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+
+ win.addEventListener("load", function onload(event) {
+ win.removeEventListener("load", onload, false);
+
+ if (win.name !== "SanitizeDialog")
+ return;
+
+ wh.win = win;
+ loaded = true;
+ executeSoon(() => wh.onload());
+ }, false);
+
+ win.addEventListener("unload", function onunload(event) {
+ if (win.name !== "SanitizeDialog") {
+ win.removeEventListener("unload", onunload, false);
+ return;
+ }
+
+ // Why is unload fired before load?
+ if (!loaded)
+ return;
+
+ win.removeEventListener("unload", onunload, false);
+ wh.win = win;
+
+ // Some exceptions that reach here don't reach the test harness, but
+ // ok()/is() do...
+ Task.spawn(function* () {
+ if (wh.onunload) {
+ yield wh.onunload();
+ }
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ wh._resolveClosed();
+ });
+ }, false);
+ }
+ Services.ww.registerNotification(windowObserver);
+ Services.ww.openWindow(null,
+ "chrome://browser/content/sanitize.xul",
+ "SanitizeDialog",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+ },
+
+ /**
+ * Selects a duration in the duration dropdown.
+ *
+ * @param aDurVal
+ * One of the Sanitizer.TIMESPAN_* values
+ */
+ selectDuration: function (aDurVal) {
+ this.getDurationDropdown().value = aDurVal;
+ if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible for TIMESPAN_EVERYTHING");
+ }
+ else {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
+ }
+ },
+
+ /**
+ * Toggles the details progressive disclosure button.
+ */
+ toggleDetails: function () {
+ this.getDetailsButton().click();
+ }
+};
+
+function promiseSanitizationComplete() {
+ return promiseTopicObserved("sanitizer-sanitization-complete");
+}
+
+/**
+ * Adds a download to history.
+ *
+ * @param aMinutesAgo
+ * The download will be downloaded this many minutes ago
+ */
+function* addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+
+ let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
+ let download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: name
+ });
+ download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin));
+ download.canceled = true;
+ publicList.add(download);
+
+ ok((yield downloadExists(name)),
+ "Sanity check: download " + name +
+ " should exist after creating it");
+
+ aExpectedPathList.push(name);
+}
+
+/**
+ * Adds a form entry to history.
+ *
+ * @param aMinutesAgo
+ * The entry will be added this many minutes ago
+ */
+function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) {
+ let name = aMinutesAgo + "-minutes-ago";
+
+ // Artifically age the entry to the proper vintage.
+ let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin);
+
+ return new Promise((resolve, reject) =>
+ FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
+ { handleError: function (error) {
+ reject();
+ throw new Error("Error occurred updating form history: " + error);
+ },
+ handleCompletion: function (reason) {
+ resolve(name);
+ }
+ })
+ )
+}
+
+/**
+ * Checks if a form entry exists.
+ */
+function formNameExists(name)
+{
+ return new Promise((resolve, reject) => {
+ let count = 0;
+ FormHistory.count({ fieldname: name },
+ { handleResult: result => count = result,
+ handleError: function (error) {
+ reject(error);
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) {
+ if (!reason) {
+ resolve(count);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Removes all history visits, downloads, and form entries.
+ */
+function* blankSlate() {
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let downloads = yield publicList.getAll();
+ for (let download of downloads) {
+ yield publicList.remove(download);
+ yield download.finalize(true);
+ }
+
+ yield new Promise((resolve, reject) => {
+ FormHistory.update({op: "remove"}, {
+ handleCompletion(reason) {
+ if (!reason) {
+ resolve();
+ }
+ },
+ handleError(error) {
+ reject(error);
+ throw new Error("Error occurred updating form history: " + error);
+ }
+ });
+ });
+
+ yield PlacesTestUtils.clearHistory();
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Checks to see if the download with the specified path exists.
+ *
+ * @param aPath
+ * The path of the download to check
+ * @return True if the download exists, false otherwise
+ */
+function* downloadExists(aPath) {
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let listArray = yield publicList.getAll();
+ return listArray.some(i => i.target.path == aPath);
+}
+
+/**
+ * Ensures that the specified downloads are either cleared or not.
+ *
+ * @param aDownloadIDs
+ * Array of download database IDs
+ * @param aShouldBeCleared
+ * True if each download should be cleared, false otherwise
+ */
+function* ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ for (let id of aDownloadIDs) {
+ is((yield downloadExists(id)), !aShouldBeCleared,
+ "download " + id + " should " + niceStr + " exist");
+ }
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function intPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ * The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+ return now_uSec - aMinutesAgo * kUsecPerMin;
+}
diff --git a/browser/base/content/test/general/browser_save_link-perwindowpb.js b/browser/base/content/test/general/browser_save_link-perwindowpb.js
new file mode 100644
index 000000000..5c99ba32a
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+// Trigger a save of a link in public mode, then trigger an identical save
+// in private mode and ensure that the second request is differentiated from
+// the first by checking that cookies set by the first response are not sent
+// during the second request.
+function triggerSave(aWindow, aCallback) {
+ info("started triggerSave");
+ var fileName;
+ let testBrowser = aWindow.gBrowser.selectedBrowser;
+ // This page sets a cookie if and only if a cookie does not exist yet
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517-2.html";
+ testBrowser.loadURI(testURI);
+ BrowserTestUtils.browserLoaded(testBrowser, false, testURI)
+ .then(() => {
+ waitForFocus(function () {
+ info("register to handle popupshown");
+ aWindow.document.addEventListener("popupshown", contextMenuOpened, false);
+
+ BrowserTestUtils.synthesizeMouseAtCenter("#fff", {type: "contextmenu", button: 2}, testBrowser);
+ info("right clicked!");
+ }, aWindow);
+ });
+
+ function contextMenuOpened(event) {
+ info("contextMenuOpened");
+ aWindow.document.removeEventListener("popupshown", contextMenuOpened);
+
+ // Create the folder the link will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ info("showCallback");
+ fileName = fp.defaultString;
+ info("fileName: " + fileName);
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ info("done showCallback");
+ };
+
+ mockTransferCallback = function(downloadSuccess) {
+ info("mockTransferCallback");
+ onTransferComplete(aWindow, downloadSuccess, destDir);
+ destDir.remove(true);
+ ok(!destDir.exists(), "Destination dir should be removed");
+ ok(!destFile.exists(), "Destination file should be removed");
+ mockTransferCallback = null;
+ info("done mockTransferCallback");
+ }
+
+ // Select "Save Link As" option from context menu
+ var saveLinkCommand = aWindow.document.getElementById("context-savelink");
+ info("saveLinkCommand: " + saveLinkCommand);
+ saveLinkCommand.doCommand();
+
+ event.target.hidePopup();
+ info("popup hidden");
+ }
+
+ function onTransferComplete(aWindow2, downloadSuccess, destDir) {
+ ok(downloadSuccess, "Link should have been downloaded successfully");
+ aWindow2.close();
+
+ executeSoon(() => aCallback());
+ }
+}
+
+function test() {
+ info("Start the test");
+ waitForExplicitFinish();
+
+ var gNumSet = 0;
+ function testOnWindow(options, callback) {
+ info("testOnWindow(" + options + ")");
+ var win = OpenBrowserWindow(options);
+ info("got " + win);
+ whenDelayedStartupFinished(win, () => callback(win));
+ }
+
+ function whenDelayedStartupFinished(aWindow, aCallback) {
+ info("whenDelayedStartupFinished");
+ Services.obs.addObserver(function obs(aSubject, aTopic) {
+ info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(obs, aTopic);
+ executeSoon(aCallback);
+ info("whenDelayedStartupFinished found our window");
+ }
+ }, "browser-delayed-startup-finished", false);
+ }
+
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ info("Running the cleanup code");
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ info("Finished running the cleanup code");
+ });
+
+ function observer(subject, topic, state) {
+ info("observer called with " + topic);
+ if (topic == "http-on-modify-request") {
+ onModifyRequest(subject);
+ } else if (topic == "http-on-examine-response") {
+ onExamineResponse(subject);
+ }
+ }
+
+ function onExamineResponse(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ info("onExamineResponse with " + channel.URI.spec);
+ if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") {
+ info("returning");
+ return;
+ }
+ try {
+ let cookies = channel.getResponseHeader("set-cookie");
+ // From browser/base/content/test/general/bug792715.sjs, we receive a Set-Cookie
+ // header with foopy=1 when there are no cookies for that domain.
+ is(cookies, "foopy=1", "Cookie should be foopy=1");
+ gNumSet += 1;
+ info("gNumSet = " + gNumSet);
+ } catch (ex) {
+ if (ex.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ info("onExamineResponse caught NOTAVAIL" + ex);
+ } else {
+ info("ionExamineResponse caught " + ex);
+ }
+ }
+ }
+
+ function onModifyRequest(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ info("onModifyRequest with " + channel.URI.spec);
+ if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") {
+ return;
+ }
+ try {
+ let cookies = channel.getRequestHeader("cookie");
+ info("cookies: " + cookies);
+ // From browser/base/content/test/general/bug792715.sjs, we should never send a
+ // cookie because we are making only 2 requests: one in public mode, and
+ // one in private mode.
+ throw "We should never send a cookie in this test";
+ } catch (ex) {
+ if (ex.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ info("onModifyRequest caught NOTAVAIL" + ex);
+ } else {
+ info("ionModifyRequest caught " + ex);
+ }
+ }
+ }
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+
+ testOnWindow(undefined, function(win) {
+ // The first save from a regular window sets a cookie.
+ triggerSave(win, function() {
+ is(gNumSet, 1, "1 cookie should be set");
+
+ // The second save from a private window also sets a cookie.
+ testOnWindow({private: true}, function(win2) {
+ triggerSave(win2, function() {
+ is(gNumSet, 2, "2 cookies should be set");
+ finish();
+ });
+ });
+ });
+ });
+}
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
diff --git a/browser/base/content/test/general/browser_save_link_when_window_navigates.js b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
new file mode 100644
index 000000000..2fd10b00e
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
+const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
+
+function triggerSave(aWindow, aCallback) {
+ info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
+ var fileName;
+ let testBrowser = aWindow.gBrowser.selectedBrowser;
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
+ windowObserver.setCallback(onUCTDialog);
+ testBrowser.loadURI(testURI);
+
+ // Create the folder the link will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ info("showCallback");
+ fileName = fp.defaultString;
+ info("fileName: " + fileName);
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ info("done showCallback");
+ };
+
+ mockTransferCallback = function(downloadSuccess) {
+ info("mockTransferCallback");
+ onTransferComplete(aWindow, downloadSuccess, destDir);
+ destDir.remove(true);
+ ok(!destDir.exists(), "Destination dir should be removed");
+ ok(!destFile.exists(), "Destination file should be removed");
+ mockTransferCallback = null;
+ info("done mockTransferCallback");
+ }
+
+ function onUCTDialog(dialog) {
+ function doLoad() {
+ content.document.querySelector('iframe').remove();
+ }
+ testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
+ executeSoon(continueDownloading);
+ }
+
+ function continueDownloading() {
+ let windows = Services.wm.getEnumerator("");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (win.location && win.location.href == UCT_URI) {
+ win.document.documentElement._fireButtonEvent("accept");
+ win.close();
+ return;
+ }
+ }
+ ok(false, "No Unknown Content Type dialog yet?");
+ }
+
+ function onTransferComplete(aWindow2, downloadSuccess) {
+ ok(downloadSuccess, "Link should have been downloaded successfully");
+ aWindow2.close();
+
+ executeSoon(aCallback);
+ }
+}
+
+
+var windowObserver = {
+ setCallback: function(aCallback) {
+ if (this._callback) {
+ ok(false, "Should only be dealing with one callback at a time.");
+ }
+ this._callback = aCallback;
+ },
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened") {
+ return;
+ }
+
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+ win.addEventListener("load", function onLoad(event) {
+ win.removeEventListener("load", onLoad, false);
+
+ if (win.location == UCT_URI) {
+ SimpleTest.executeSoon(function() {
+ if (windowObserver._callback) {
+ windowObserver._callback(win);
+ delete windowObserver._callback;
+ } else {
+ ok(false, "Unexpected UCT dialog!");
+ }
+ });
+ }
+ }, false);
+ }
+};
+
+Services.ww.registerNotification(windowObserver);
+
+function test() {
+ waitForExplicitFinish();
+
+ function testOnWindow(options, callback) {
+ info("testOnWindow(" + options + ")");
+ var win = OpenBrowserWindow(options);
+ info("got " + win);
+ whenDelayedStartupFinished(win, () => callback(win));
+ }
+
+ function whenDelayedStartupFinished(aWindow, aCallback) {
+ info("whenDelayedStartupFinished");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ info("whenDelayedStartupFinished found our window");
+ }
+ }, "browser-delayed-startup-finished", false);
+ }
+
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ info("Running the cleanup code");
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ Services.ww.unregisterNotification(windowObserver);
+ Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
+ Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+ info("Finished running the cleanup code");
+ });
+
+ Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
+ testOnWindow(undefined, function(win) {
+ let windowGonePromise = promiseWindowWillBeClosed(win);
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
+ triggerSave(win, function() {
+ windowGonePromise.then(function() {
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
+ testOnWindow(undefined, function(win2) {
+ triggerSave(win2, finish);
+ });
+ });
+ });
+ });
+}
+
diff --git a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
new file mode 100644
index 000000000..e7ed5fa34
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -0,0 +1,116 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+
+function promiseNoCacheEntry(filename) {
+ return new Promise((resolve, reject) => {
+ Visitor.prototype = {
+ onCacheStorageInfo: function(num, consumption)
+ {
+ info("disk storage contains " + num + " entries");
+ },
+ onCacheEntryInfo: function(uri)
+ {
+ let urispec = uri.asciiSpec;
+ info(urispec);
+ is(urispec.includes(filename), false, "web content present in disk cache");
+ },
+ onCacheEntryVisitCompleted: function()
+ {
+ resolve();
+ }
+ };
+ function Visitor() {}
+
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
+ let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+ storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
+ });
+}
+
+function promiseImageDownloaded() {
+ return new Promise((resolve, reject) => {
+ let fileName;
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "Image file should have been downloaded successfully " + fileName);
+
+ // Give the request a chance to finish and create a cache entry
+ resolve(fileName);
+ }
+
+ // Create the folder the image will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferCallback = null;
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ });
+}
+
+add_task(function* () {
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html";
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let tab = yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, testURI);
+
+ let contextMenu = privateWindow.document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#img", {
+ type: "contextmenu",
+ button: 2
+ }, tab.linkedBrowser);
+ yield popupShown;
+
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ cache.clear();
+
+ let imageDownloaded = promiseImageDownloaded();
+ // Select "Save Image As" option from context menu
+ privateWindow.document.getElementById("context-saveimage").doCommand();
+
+ contextMenu.hidePopup();
+ yield popupHidden;
+
+ // wait for image download
+ let fileName = yield imageDownloaded;
+ yield promiseNoCacheEntry(fileName);
+
+ yield BrowserTestUtils.closeWindow(privateWindow);
+});
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
diff --git a/browser/base/content/test/general/browser_save_video.js b/browser/base/content/test/general/browser_save_video.js
new file mode 100644
index 000000000..e81286b7a
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_video.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+/**
+ * TestCase for bug 564387
+ * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387>
+ */
+add_task(function* () {
+ var fileName;
+
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html");
+ yield loadPromise;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#video1",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser);
+ info("context menu click on video1");
+
+ yield popupShownPromise;
+
+ info("context menu opened on video1");
+
+ // Create the folder the video will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append(fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ let transferCompletePromise = new Promise((resolve) => {
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "Video file should have been downloaded successfully");
+
+ is(fileName, "web-video1-expectedName.ogv",
+ "Video file name is correctly retrieved from Content-Disposition http header");
+ resolve();
+ }
+
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+ });
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ // Select "Save Video As" option from context menu
+ var saveVideoCommand = document.getElementById("context-savevideo");
+ saveVideoCommand.doCommand();
+ info("context-savevideo command executed");
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield popupHiddenPromise;
+
+ yield transferCompletePromise;
+});
+
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
diff --git a/browser/base/content/test/general/browser_save_video_frame.js b/browser/base/content/test/general/browser_save_video_frame.js
new file mode 100644
index 000000000..e9b8a0475
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_video_frame.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const VIDEO_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html";
+
+/**
+ * mockTransfer.js provides a utility that lets us mock out
+ * the "Save File" dialog.
+ */
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+/**
+ * Creates and returns an nsIFile for a new temporary save
+ * directory.
+ *
+ * @return nsIFile
+ */
+function createTemporarySaveDirectory() {
+ let saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+/**
+ * MockTransfer exposes a "mockTransferCallback" global which
+ * allows us to define a callback to be called once the mock file
+ * selector has selected where to save the file.
+ */
+function waitForTransferComplete() {
+ return new Promise((resolve) => {
+ mockTransferCallback = () => {
+ ok(true, "Transfer completed");
+ resolve();
+ }
+ });
+}
+
+/**
+ * Given some browser, loads a framescript that right-clicks
+ * on the video1 element to spawn a contextmenu.
+ */
+function rightClickVideo(browser) {
+ let frame_script = () => {
+ const Ci = Components.interfaces;
+ let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let document = content.document;
+ let video = document.getElementById("video1");
+ let rect = video.getBoundingClientRect();
+
+ /* Synthesize a click in the center of the video. */
+ let left = rect.left + (rect.width / 2);
+ let top = rect.top + (rect.height / 2);
+
+ utils.sendMouseEvent("contextmenu", left, top,
+ 2, /* aButton */
+ 1, /* aClickCount */
+ 0 /* aModifiers */);
+ };
+ let mm = browser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+}
+
+/**
+ * Loads a page with a <video> element, right-clicks it and chooses
+ * to save a frame screenshot to the disk. Completes once we've
+ * verified that the frame has been saved to disk.
+ */
+add_task(function*() {
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+
+ // Create the folder the video will be saved into.
+ let destDir = createTemporarySaveDirectory();
+ let destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ destFile.append(fp.defaultString);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferRegisterer.register();
+
+ // Make sure that we clean these things up when we're done.
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+ info("Loading video tab");
+ yield promiseTabLoadEvent(tab, VIDEO_URL);
+ info("Video tab loaded.");
+
+ let context = document.getElementById("contentAreaContextMenu");
+ let popupPromise = promisePopupShown(context);
+
+ info("Synthesizing right-click on video element");
+ rightClickVideo(browser);
+ info("Waiting for popup to fire popupshown.");
+ yield popupPromise;
+ info("Popup fired popupshown");
+
+ let saveSnapshotCommand = document.getElementById("context-video-saveimage");
+ let promiseTransfer = waitForTransferComplete()
+ info("Firing save snapshot command");
+ saveSnapshotCommand.doCommand();
+ context.hidePopup();
+ info("Waiting for transfer completion");
+ yield promiseTransfer;
+ info("Transfer complete");
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_scope.js b/browser/base/content/test/general/browser_scope.js
new file mode 100644
index 000000000..f8141e5f6
--- /dev/null
+++ b/browser/base/content/test/general/browser_scope.js
@@ -0,0 +1,10 @@
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null");
+
+function test() {
+ ok(!!gBrowser, "gBrowser exists");
+ is(gBrowser, getBrowser(), "both ways of getting tabbrowser work");
+}
diff --git a/browser/base/content/test/general/browser_selectTabAtIndex.js b/browser/base/content/test/general/browser_selectTabAtIndex.js
new file mode 100644
index 000000000..b6578aec0
--- /dev/null
+++ b/browser/base/content/test/general/browser_selectTabAtIndex.js
@@ -0,0 +1,81 @@
+"use strict";
+
+function test() {
+ const isLinux = navigator.platform.indexOf("Linux") == 0;
+
+ function assertTab(expectedTab) {
+ is(gBrowser.tabContainer.selectedIndex, expectedTab,
+ `tab index ${expectedTab} should be selected`);
+ }
+
+ function sendAccelKey(key) {
+ // Make sure the keystroke goes to chrome.
+ document.activeElement.blur();
+ EventUtils.synthesizeKey(key.toString(), { altKey: isLinux, accelKey: !isLinux });
+ }
+
+ function createTabs(count) {
+ for (let n = 0; n < count; n++)
+ gBrowser.addTab();
+ }
+
+ function testKey(key, expectedTab) {
+ sendAccelKey(key);
+ assertTab(expectedTab);
+ }
+
+ function testIndex(index, expectedTab) {
+ gBrowser.selectTabAtIndex(index);
+ assertTab(expectedTab);
+ }
+
+ // Create fewer tabs than our 9 number keys.
+ is(gBrowser.tabs.length, 1, "should have 1 tab");
+ createTabs(4);
+ is(gBrowser.tabs.length, 5, "should have 5 tabs");
+
+ // Test keyboard shortcuts. Order tests so that no two test cases have the
+ // same expected tab in a row. This ensures that tab selection actually
+ // changed the selected tab.
+ testKey(8, 4);
+ testKey(1, 0);
+ testKey(2, 1);
+ testKey(4, 3);
+ testKey(9, 4);
+
+ // Test index selection.
+ testIndex(0, 0);
+ testIndex(4, 4);
+ testIndex(-5, 0);
+ testIndex(5, 4);
+ testIndex(-4, 1);
+ testIndex(1, 1);
+ testIndex(-1, 4);
+ testIndex(9, 4);
+
+ // Create more tabs than our 9 number keys.
+ createTabs(10);
+ is(gBrowser.tabs.length, 15, "should have 15 tabs");
+
+ // Test keyboard shortcuts.
+ testKey(2, 1);
+ testKey(1, 0);
+ testKey(4, 3);
+ testKey(8, 7);
+ testKey(9, 14);
+
+ // Test index selection.
+ testIndex(-15, 0);
+ testIndex(14, 14);
+ testIndex(-14, 1);
+ testIndex(15, 14);
+ testIndex(-1, 14);
+ testIndex(0, 0);
+ testIndex(1, 1);
+ testIndex(9, 9);
+
+ // Clean up tabs.
+ for (let n = 15; n > 1; n--)
+ gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
+ is(gBrowser.tabs.length, 1, "should have 1 tab");
+}
diff --git a/browser/base/content/test/general/browser_selectpopup.js b/browser/base/content/test/general/browser_selectpopup.js
new file mode 100644
index 000000000..d34254d1c
--- /dev/null
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -0,0 +1,563 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks that a <select> with an <optgroup> opens and can be navigated
+// in a child process. This is different than single-process as a <menulist> is used
+// to implement the dropdown list.
+
+requestLongerTimeout(2);
+
+const XHTML_DTD = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
+
+const PAGECONTENT =
+ "<html xmlns='http://www.w3.org/1999/xhtml'>" +
+ "<body onload='gChangeEvents = 0;gInputEvents = 0; document.body.firstChild.focus()'><select oninput='gInputEvents++' onchange='gChangeEvents++'>" +
+ " <optgroup label='First Group'>" +
+ " <option value='One'>One</option>" +
+ " <option value='Two'>Two</option>" +
+ " </optgroup>" +
+ " <option value='Three'>Three</option>" +
+ " <optgroup label='Second Group' disabled='true'>" +
+ " <option value='Four'>Four</option>" +
+ " <option value='Five'>Five</option>" +
+ " </optgroup>" +
+ " <option value='Six' disabled='true'>Six</option>" +
+ " <optgroup label='Third Group'>" +
+ " <option value='Seven'> Seven </option>" +
+ " <option value='Eight'>&nbsp;&nbsp;Eight&nbsp;&nbsp;</option>" +
+ " </optgroup></select><input />Text" +
+ "</body></html>";
+
+const PAGECONTENT_SMALL =
+ "<html>" +
+ "<body><select id='one'>" +
+ " <option value='One'>One</option>" +
+ " <option value='Two'>Two</option>" +
+ "</select><select id='two'>" +
+ " <option value='Three'>Three</option>" +
+ " <option value='Four'>Four</option>" +
+ "</select><select id='three'>" +
+ " <option value='Five'>Five</option>" +
+ " <option value='Six'>Six</option>" +
+ "</select></body></html>";
+
+const PAGECONTENT_SOMEHIDDEN =
+ "<html><head><style>.hidden { display: none; }</style></head>" +
+ "<body><select id='one'>" +
+ " <option value='One' style='display: none;'>OneHidden</option>" +
+ " <option value='Two' class='hidden'>TwoHidden</option>" +
+ " <option value='Three'>ThreeVisible</option>" +
+ " <option value='Four'style='display: table;'>FourVisible</option>" +
+ " <option value='Five'>FiveVisible</option>" +
+ " <optgroup label='GroupHidden' class='hidden'>" +
+ " <option value='Four'>Six.OneHidden</option>" +
+ " <option value='Five' style='display: block;'>Six.TwoHidden</option>" +
+ " </optgroup>" +
+ " <option value='Six' class='hidden' style='display: block;'>SevenVisible</option>" +
+ "</select></body></html>";
+
+const PAGECONTENT_TRANSLATED =
+ "<html><body>" +
+ "<div id='div'>" +
+ "<iframe id='frame' width='320' height='295' style='border: none;'" +
+ " src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
+ "</iframe>" +
+ "</div></body></html>";
+
+function openSelectPopup(selectPopup, withMouse, selector = "select", win = window)
+{
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+
+ if (withMouse) {
+ return Promise.all([popupShownPromise,
+ BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser)]);
+ }
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }, win);
+ return popupShownPromise;
+}
+
+function hideSelectPopup(selectPopup, mode = "enter", win = window)
+{
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+
+ if (mode == "escape") {
+ EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" }, win);
+ }
+ else if (mode == "enter") {
+ EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" }, win);
+ }
+ else if (mode == "click") {
+ EventUtils.synthesizeMouseAtCenter(selectPopup.lastChild, { }, win);
+ }
+
+ return popupHiddenPromise;
+}
+
+function getInputEvents()
+{
+ return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ return content.wrappedJSObject.gInputEvents;
+ });
+}
+
+function getChangeEvents()
+{
+ return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ return content.wrappedJSObject.gChangeEvents;
+ });
+}
+
+function* doSelectTests(contentType, dtd)
+{
+ const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ yield openSelectPopup(selectPopup);
+
+ let isWindows = navigator.platform.indexOf("Win") >= 0;
+
+ is(menulist.selectedIndex, 1, "Initial selection");
+ is(selectPopup.firstChild.localName, "menucaption", "optgroup is caption");
+ is(selectPopup.firstChild.getAttribute("label"), "First Group", "optgroup label");
+ is(selectPopup.childNodes[1].localName, "menuitem", "option is menuitem");
+ is(selectPopup.childNodes[1].getAttribute("label"), "One", "option label");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(2), "Select item 2");
+ is(menulist.selectedIndex, isWindows ? 2 : 1, "Select item 2 selectedIndex");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3");
+ is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+
+ // On Windows, one can navigate on disabled menuitems
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(9),
+ "Skip optgroup header and disabled items select item 7");
+ is(menulist.selectedIndex, isWindows ? 9 : 1, "Select or skip disabled item selectedIndex");
+
+ for (let i = 0; i < 10; i++) {
+ is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled")
+ }
+
+ EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
+ is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
+
+ is((yield getInputEvents()), 0, "Before closed - number of input events");
+ is((yield getChangeEvents()), 0, "Before closed - number of change events");
+
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { isWindows }, function(args) {
+ Assert.equal(String(content.getSelection()), args.isWindows ? "Text" : "",
+ "Select all while popup is open");
+ });
+
+ // Backspace should not go back
+ let handleKeyPress = function(event) {
+ ok(false, "Should not get keypress event");
+ }
+ window.addEventListener("keypress", handleKeyPress);
+ EventUtils.synthesizeKey("VK_BACK_SPACE", { });
+ window.removeEventListener("keypress", handleKeyPress);
+
+ yield hideSelectPopup(selectPopup);
+
+ is(menulist.selectedIndex, 3, "Item 3 still selected");
+ is((yield getInputEvents()), 1, "After closed - number of input events");
+ is((yield getChangeEvents()), 1, "After closed - number of change events");
+
+ // Opening and closing the popup without changing the value should not fire a change event.
+ yield openSelectPopup(selectPopup, true);
+ yield hideSelectPopup(selectPopup, "escape");
+ is((yield getInputEvents()), 1, "Open and close with no change - number of input events");
+ is((yield getChangeEvents()), 1, "Open and close with no change - number of change events");
+ EventUtils.synthesizeKey("VK_TAB", { });
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+ is((yield getInputEvents()), 1, "Tab away from select with no change - number of input events");
+ is((yield getChangeEvents()), 1, "Tab away from select with no change - number of change events");
+
+ yield openSelectPopup(selectPopup, true);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ yield hideSelectPopup(selectPopup, "escape");
+ is((yield getInputEvents()), isWindows ? 2 : 1, "Open and close with change - number of input events");
+ is((yield getChangeEvents()), isWindows ? 2 : 1, "Open and close with change - number of change events");
+ EventUtils.synthesizeKey("VK_TAB", { });
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+ is((yield getInputEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of input events");
+ is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events");
+
+ is(selectPopup.lastChild.previousSibling.label, "Seven", "Spaces collapsed");
+ is(selectPopup.lastChild.label, "\xA0\xA0Eight\xA0\xA0", "Non-breaking spaces not collapsed");
+
+ yield BrowserTestUtils.removeTab(tab);
+}
+
+add_task(function*() {
+ yield doSelectTests("text/html", "");
+});
+
+add_task(function*() {
+ yield doSelectTests("application/xhtml+xml", XHTML_DTD);
+});
+
+// This test opens a select popup and removes the content node of a popup while
+// The popup should close if its node is removed.
+add_task(function*() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ // First, try it when a different <select> element than the one that is open is removed
+ yield openSelectPopup(selectPopup, true, "#one");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ content.document.body.removeChild(content.document.getElementById("two"));
+ });
+
+ // Wait a bit just to make sure the popup won't close.
+ yield new Promise(resolve => setTimeout(resolve, 1000));
+
+ is(selectPopup.state, "open", "Different popup did not affect open popup");
+
+ yield hideSelectPopup(selectPopup);
+
+ // Next, try it when the same <select> element than the one that is open is removed
+ yield openSelectPopup(selectPopup, true, "#three");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ content.document.body.removeChild(content.document.getElementById("three"));
+ });
+ yield popupHiddenPromise;
+
+ ok(true, "Popup hidden when select is removed");
+
+ // Finally, try it when the tab is closed while the select popup is open.
+ yield openSelectPopup(selectPopup, true, "#one");
+
+ popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+ yield BrowserTestUtils.removeTab(tab);
+ yield popupHiddenPromise;
+
+ ok(true, "Popup hidden when tab is closed");
+});
+
+// This test opens a select popup that is isn't a frame and has some translations applied.
+add_task(function*() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ // First, get the position of the select popup when no translations have been applied.
+ yield openSelectPopup(selectPopup, false);
+
+ let rect = selectPopup.getBoundingClientRect();
+ let expectedX = rect.left;
+ let expectedY = rect.top;
+
+ yield hideSelectPopup(selectPopup);
+
+ // Iterate through a set of steps which each add more translation to the select's expected position.
+ let steps = [
+ [ "div", "transform: translateX(7px) translateY(13px);", 7, 13 ],
+ [ "frame", "border-top: 5px solid green; border-left: 10px solid red; border-right: 35px solid blue;", 10, 5 ],
+ [ "frame", "border: none; padding-left: 6px; padding-right: 12px; padding-top: 2px;", -4, -3 ],
+ [ "select", "margin: 9px; transform: translateY(-3px);", 9, 6 ],
+ ];
+
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
+ let step = steps[stepIndex];
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, step, function*(contentStep) {
+ return new Promise(resolve => {
+ let changedWin = content;
+
+ let elem;
+ if (contentStep[0] == "select") {
+ changedWin = content.document.getElementById("frame").contentWindow;
+ elem = changedWin.document.getElementById("select");
+ }
+ else {
+ elem = content.document.getElementById(contentStep[0]);
+ }
+
+ changedWin.addEventListener("MozAfterPaint", function onPaint() {
+ changedWin.removeEventListener("MozAfterPaint", onPaint);
+ resolve();
+ });
+
+ elem.style = contentStep[1];
+ elem.getBoundingClientRect();
+ });
+ });
+
+ yield openSelectPopup(selectPopup, false);
+
+ expectedX += step[2];
+ expectedY += step[3];
+
+ let popupRect = selectPopup.getBoundingClientRect();
+ is(popupRect.left, expectedX, "step " + (stepIndex + 1) + " x");
+ is(popupRect.top, expectedY, "step " + (stepIndex + 1) + " y");
+
+ yield hideSelectPopup(selectPopup);
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// Test that we get the right events when a select popup is changed.
+add_task(function* test_event_order() {
+ const URL = "data:text/html," + escape(PAGECONTENT_SMALL);
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: URL,
+ }, function*(browser) {
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ // According to https://html.spec.whatwg.org/#the-select-element,
+ // we want to fire input, change, and then click events on the
+ // <select> (in that order) when it has changed.
+ let expectedEnter = [
+ {
+ type: "input",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ {
+ type: "change",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ ];
+
+ let expectedClick = [
+ {
+ type: "mousedown",
+ cancelable: true,
+ targetIsOption: true,
+ },
+ {
+ type: "mouseup",
+ cancelable: true,
+ targetIsOption: true,
+ },
+ {
+ type: "input",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ {
+ type: "change",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ {
+ type: "click",
+ cancelable: true,
+ targetIsOption: true,
+ },
+ ];
+
+ for (let mode of ["enter", "click"]) {
+ let expected = mode == "enter" ? expectedEnter : expectedClick;
+ yield openSelectPopup(selectPopup, true, mode == "enter" ? "#one" : "#two");
+
+ let eventsPromise = ContentTask.spawn(browser, [mode, expected], function*([contentMode, contentExpected]) {
+ return new Promise((resolve) => {
+ function onEvent(event) {
+ select.removeEventListener(event.type, onEvent);
+ Assert.ok(contentExpected.length, "Unexpected event " + event.type);
+ let expectation = contentExpected.shift();
+ Assert.equal(event.type, expectation.type,
+ "Expected the right event order");
+ Assert.ok(event.bubbles, "All of these events should bubble");
+ Assert.equal(event.cancelable, expectation.cancelable,
+ "Cancellation property should match");
+ Assert.equal(event.target.localName,
+ expectation.targetIsOption ? "option" : "select",
+ "Target matches");
+ if (!contentExpected.length) {
+ resolve();
+ }
+ }
+
+ let select = content.document.getElementById(contentMode == "enter" ? "one" : "two");
+ for (let event of ["input", "change", "mousedown", "mouseup", "click"]) {
+ select.addEventListener(event, onEvent);
+ }
+ });
+ });
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ yield hideSelectPopup(selectPopup, mode);
+ yield eventsPromise;
+ }
+ });
+});
+
+function* performLargePopupTests(win)
+{
+ let browser = win.gBrowser.selectedBrowser;
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let doc = content.document;
+ let select = doc.getElementById("one");
+ for (var i = 0; i < 180; i++) {
+ select.add(new content.Option("Test" + i));
+ }
+
+ select.options[60].selected = true;
+ select.focus();
+ });
+
+ let selectPopup = win.document.getElementById("ContentSelectDropdown").menupopup;
+ let browserRect = browser.getBoundingClientRect();
+
+ let positions = [
+ "margin-top: 300px;",
+ "position: fixed; bottom: 100px;",
+ "width: 100%; height: 9999px;"
+ ];
+
+ let position;
+ while (true) {
+ yield openSelectPopup(selectPopup, false, "select", win);
+
+ let rect = selectPopup.getBoundingClientRect();
+ ok(rect.top >= browserRect.top, "Popup top position in within browser area");
+ ok(rect.bottom <= browserRect.bottom, "Popup bottom position in within browser area");
+
+ // Don't check the scroll position for the last step as the popup will be cut off.
+ if (positions.length > 0) {
+ let cs = win.getComputedStyle(selectPopup);
+ let bpBottom = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth);
+
+ is(selectPopup.childNodes[60].getBoundingClientRect().bottom,
+ selectPopup.getBoundingClientRect().bottom - bpBottom,
+ "Popup scroll at correct position " + bpBottom);
+ }
+
+ yield hideSelectPopup(selectPopup, "enter", win);
+
+ position = positions.shift();
+ if (!position) {
+ break;
+ }
+
+ let contentPainted = BrowserTestUtils.contentPainted(browser);
+ yield ContentTask.spawn(browser, position, function*(contentPosition) {
+ let select = content.document.getElementById("one");
+ select.setAttribute("style", contentPosition);
+ select.getBoundingClientRect();
+ });
+ yield contentPainted;
+ }
+}
+
+// This test checks select elements with a large number of options to ensure that
+// the popup appears within the browser area.
+add_task(function* test_large_popup() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ yield* performLargePopupTests(window);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// This test checks the same as the previous test but in a new smaller window.
+add_task(function* test_large_popup_in_small_window() {
+ let newwin = yield BrowserTestUtils.openNewBrowserWindow({ width: 400, height: 400 });
+
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(newwin.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.loadURI(newwin.gBrowser.selectedBrowser, pageUrl);
+ yield browserLoadedPromise;
+
+ newwin.gBrowser.selectedBrowser.focus();
+
+ yield* performLargePopupTests(newwin);
+
+ yield BrowserTestUtils.closeWindow(newwin);
+});
+
+// This test checks that a mousemove event is fired correctly at the menu and
+// not at the browser, ensuring that any mouse capture has been cleared.
+add_task(function* test_mousemove_correcttarget() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ yield new Promise(resolve => {
+ window.addEventListener("mousemove", function checkForMouseMove(event) {
+ window.removeEventListener("mousemove", checkForMouseMove, true);
+ is(event.target.localName.indexOf("menu"), 0, "mouse over menu");
+ resolve();
+ }, true);
+
+ EventUtils.synthesizeMouseAtCenter(selectPopup.firstChild, { type: "mousemove" });
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mouseup" }, gBrowser.selectedBrowser);
+
+ yield hideSelectPopup(selectPopup);
+
+ // The popup should be closed when fullscreen mode is entered or exited.
+ for (let steps = 0; steps < 2; steps++) {
+ yield openSelectPopup(selectPopup, true);
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+ let sizeModeChanged = BrowserTestUtils.waitForEvent(window, "sizemodechange");
+ BrowserFullScreen();
+ yield sizeModeChanged;
+ yield popupHiddenPromise;
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// This test checks when a <select> element has some options with altered display values.
+add_task(function* test_somehidden() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SOMEHIDDEN);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ // The exact number is not needed; just ensure the height is larger than 4 items to accomodate any popup borders.
+ ok(selectPopup.getBoundingClientRect().height >= selectPopup.lastChild.getBoundingClientRect().height * 4, "Height contains at least 4 items");
+ ok(selectPopup.getBoundingClientRect().height < selectPopup.lastChild.getBoundingClientRect().height * 5, "Height doesn't contain 5 items");
+
+ // The label contains the substring 'Visible' for items that are visible.
+ // Otherwise, it is expected to be display: none.
+ is(selectPopup.parentNode.itemCount, 9, "Correct number of items");
+ let child = selectPopup.firstChild;
+ let idx = 1;
+ while (child) {
+ is(getComputedStyle(child).display, child.label.indexOf("Visible") > 0 ? "-moz-box" : "none",
+ "Item " + (idx++) + " is visible");
+ child = child.nextSibling;
+ }
+
+ yield hideSelectPopup(selectPopup, "escape");
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_ssl_error_reports.js b/browser/base/content/test/general/browser_ssl_error_reports.js
new file mode 100644
index 000000000..b1b1c8b84
--- /dev/null
+++ b/browser/base/content/test/general/browser_ssl_error_reports.js
@@ -0,0 +1,174 @@
+"use strict";
+
+const URL_REPORTS = "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?";
+const URL_BAD_CHAIN = "https://badchain.include-subdomains.pinning.example.com/";
+const URL_NO_CERT = "https://fail-handshake.example.com/";
+const URL_BAD_CERT = "https://expired.example.com/";
+const URL_BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443/";
+const ROOT = getRootDirectory(gTestPath);
+const PREF_REPORT_ENABLED = "security.ssl.errorReporting.enabled";
+const PREF_REPORT_AUTOMATIC = "security.ssl.errorReporting.automatic";
+const PREF_REPORT_URL = "security.ssl.errorReporting.url";
+
+SimpleTest.requestCompleteLog();
+
+Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
+
+function cleanup() {
+ Services.prefs.clearUserPref(PREF_REPORT_ENABLED);
+ Services.prefs.clearUserPref(PREF_REPORT_AUTOMATIC);
+ Services.prefs.clearUserPref(PREF_REPORT_URL);
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
+ cleanup();
+});
+
+add_task(function* test_send_report_neterror() {
+ yield testSendReportAutomatically(URL_BAD_CHAIN, "succeed", "neterror");
+ yield testSendReportAutomatically(URL_NO_CERT, "nocert", "neterror");
+ yield testSetAutomatic(URL_NO_CERT, "nocert", "neterror");
+});
+
+
+add_task(function* test_send_report_certerror() {
+ yield testSendReportAutomatically(URL_BAD_CERT, "badcert", "certerror");
+ yield testSetAutomatic(URL_BAD_CERT, "badcert", "certerror");
+});
+
+add_task(function* test_send_disabled() {
+ Services.prefs.setBoolPref(PREF_REPORT_ENABLED, false);
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
+ Services.prefs.setCharPref(PREF_REPORT_URL, "https://example.com/invalid");
+
+ // Check with enabled=false but automatic=true.
+ yield testSendReportDisabled(URL_NO_CERT, "neterror");
+ yield testSendReportDisabled(URL_BAD_CERT, "certerror");
+
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
+
+ // Check again with both prefs false.
+ yield testSendReportDisabled(URL_NO_CERT, "neterror");
+ yield testSendReportDisabled(URL_BAD_CERT, "certerror");
+ cleanup();
+});
+
+function* testSendReportAutomatically(testURL, suffix, errorURISuffix) {
+ Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
+ Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
+
+ // Add a tab and wait until it's loaded.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ let browser = tab.linkedBrowser;
+
+ // Load the page and wait for the error report submission.
+ let promiseStatus = createReportResponseStatusPromise(URL_REPORTS + suffix);
+ browser.loadURI(testURL);
+ yield promiseErrorPageLoaded(browser);
+
+ ok(!isErrorStatus(yield promiseStatus),
+ "SSL error report submitted successfully");
+
+ // Check that we loaded the right error page.
+ yield checkErrorPage(browser, errorURISuffix);
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+ cleanup();
+}
+
+function* testSetAutomatic(testURL, suffix, errorURISuffix) {
+ Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
+ Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
+
+ // Add a tab and wait until it's loaded.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ let browser = tab.linkedBrowser;
+
+ // Load the page.
+ browser.loadURI(testURL);
+ yield promiseErrorPageLoaded(browser);
+
+ // Check that we loaded the right error page.
+ yield checkErrorPage(browser, errorURISuffix);
+
+ let statusPromise = createReportResponseStatusPromise(URL_REPORTS + suffix);
+
+ // Click the checkbox, enable automatic error reports.
+ yield ContentTask.spawn(browser, null, function* () {
+ content.document.getElementById("automaticallyReportInFuture").click();
+ });
+
+ // Wait for the error report submission.
+ yield statusPromise;
+
+ let isAutomaticReportingEnabled = () =>
+ Services.prefs.getBoolPref(PREF_REPORT_AUTOMATIC);
+
+ // Check that the pref was flipped.
+ ok(isAutomaticReportingEnabled(), "automatic SSL report submission enabled");
+
+ // Disable automatic error reports.
+ yield ContentTask.spawn(browser, null, function* () {
+ content.document.getElementById("automaticallyReportInFuture").click();
+ });
+
+ // Check that the pref was flipped.
+ ok(!isAutomaticReportingEnabled(), "automatic SSL report submission disabled");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+ cleanup();
+}
+
+function* testSendReportDisabled(testURL, errorURISuffix) {
+ // Add a tab and wait until it's loaded.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ let browser = tab.linkedBrowser;
+
+ // Load the page.
+ browser.loadURI(testURL);
+ yield promiseErrorPageLoaded(browser);
+
+ // Check that we loaded the right error page.
+ yield checkErrorPage(browser, errorURISuffix);
+
+ // Check that the error reporting section is hidden.
+ yield ContentTask.spawn(browser, null, function* () {
+ let section = content.document.getElementById("certificateErrorReporting");
+ Assert.equal(content.getComputedStyle(section).display, "none",
+ "error reporting section should be hidden");
+ });
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+}
+
+function isErrorStatus(status) {
+ return status < 200 || status >= 300;
+}
+
+// use the observer service to see when a report is sent
+function createReportResponseStatusPromise(expectedURI) {
+ return new Promise(resolve => {
+ let observer = (subject, topic, data) => {
+ subject.QueryInterface(Ci.nsIHttpChannel);
+ let requestURI = subject.URI.spec;
+ if (requestURI == expectedURI) {
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ resolve(subject.responseStatus);
+ }
+ };
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+ });
+}
+
+function checkErrorPage(browser, suffix) {
+ return ContentTask.spawn(browser, { suffix }, function* (args) {
+ let uri = content.document.documentURI;
+ Assert.ok(uri.startsWith(`about:${args.suffix}`), "correct error page loaded");
+ });
+}
diff --git a/browser/base/content/test/general/browser_star_hsts.js b/browser/base/content/test/general/browser_star_hsts.js
new file mode 100644
index 000000000..c52e563bc
--- /dev/null
+++ b/browser/base/content/test/general/browser_star_hsts.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var secureURL = "https://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs";
+var unsecureURL = "http://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs";
+
+add_task(function* test_star_redirect() {
+ registerCleanupFunction(function() {
+ // Ensure to remove example.com from the HSTS list.
+ let sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+ sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS,
+ NetUtil.newURI("http://example.com/"), 0);
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ gBrowser.removeCurrentTab();
+ });
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ // This will add the page to the HSTS cache.
+ yield promiseTabLoadEvent(tab, secureURL, secureURL);
+ // This should transparently be redirected to the secure page.
+ yield promiseTabLoadEvent(tab, unsecureURL, secureURL);
+
+ yield promiseStarState(BookmarkingUI.STATUS_UNSTARRED);
+
+ let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI);
+ BookmarkingUI.star.click();
+ // This resolves on the next tick, so the star should have already been
+ // updated at that point.
+ yield promiseBookmark;
+
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred");
+});
+
+/**
+ * Waits for the star to reflect the expected state.
+ */
+function promiseStarState(aValue) {
+ let deferred = Promise.defer();
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ (function checkState() {
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(checkState, 1000);
+ } else {
+ deferred.resolve();
+ }
+ })();
+ return deferred.promise;
+}
+
+/**
+ * Starts a load in an existing tab and waits for it to finish (via some event).
+ *
+ * @param aTab
+ * The tab to load into.
+ * @param aUrl
+ * The url to load.
+ * @param [optional] aFinalURL
+ * The url to wait for, same as aURL if not defined.
+ * @return {Promise} resolved when the event is handled.
+ */
+function promiseTabLoadEvent(aTab, aURL, aFinalURL)
+{
+ if (!aFinalURL)
+ aFinalURL = aURL;
+ let deferred = Promise.defer();
+ info("Wait for load tab event");
+ aTab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank" ||
+ event.target.location.href != aFinalURL) {
+ info("skipping spurious load event");
+ return;
+ }
+ aTab.linkedBrowser.removeEventListener("load", load, true);
+ info("Tab load event received");
+ deferred.resolve();
+ }, true, true);
+ aTab.linkedBrowser.loadURI(aURL);
+ return deferred.promise;
+}
diff --git a/browser/base/content/test/general/browser_star_hsts.sjs b/browser/base/content/test/general/browser_star_hsts.sjs
new file mode 100644
index 000000000..10c7aae12
--- /dev/null
+++ b/browser/base/content/test/general/browser_star_hsts.sjs
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response)
+{
+ let page = "<!DOCTYPE html><html><body><p>HSTS page</p></body></html>";
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ response.setHeader("Strict-Transport-Security", "max-age=60");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", page.length + "", false);
+ response.write(page);
+}
diff --git a/browser/base/content/test/general/browser_subframe_favicons_not_used.js b/browser/base/content/test/general/browser_subframe_favicons_not_used.js
new file mode 100644
index 000000000..7efe78d9b
--- /dev/null
+++ b/browser/base/content/test/general/browser_subframe_favicons_not_used.js
@@ -0,0 +1,20 @@
+/* Make sure <link rel="..."> isn't respected in sub-frames. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let testPath = getRootDirectory(gTestPath);
+
+ let tab = gBrowser.addTab(testPath + "file_bug970276_popup1.html");
+
+ tab.linkedBrowser.addEventListener("load", function() {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let expectedIcon = testPath + "file_bug970276_favicon1.ico";
+ is(gBrowser.getIcon(tab), expectedIcon, "Correct icon.");
+
+ gBrowser.removeTab(tab);
+
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_syncui.js b/browser/base/content/test/general/browser_syncui.js
new file mode 100644
index 000000000..daf0fa497
--- /dev/null
+++ b/browser/base/content/test/general/browser_syncui.js
@@ -0,0 +1,205 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
+var {Weave} = Cu.import("resource://services-sync/main.js", {});
+
+var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://weave/locale/services/sync.properties");
+
+// ensure test output sees log messages.
+Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender());
+
+// Send the specified sync-related notification and return a promise that
+// resolves once gSyncUI._promiseUpateUI is complete and the UI is ready to check.
+function notifyAndPromiseUIUpdated(topic) {
+ return new Promise(resolve => {
+ // Instrument gSyncUI so we know when the update is complete.
+ let oldPromiseUpdateUI = gSyncUI._promiseUpdateUI.bind(gSyncUI);
+ gSyncUI._promiseUpdateUI = function() {
+ return oldPromiseUpdateUI().then(() => {
+ // Restore our override.
+ gSyncUI._promiseUpdateUI = oldPromiseUpdateUI;
+ // Resolve the promise so the caller knows the update is done.
+ resolve();
+ });
+ };
+ // Now send the notification.
+ Services.obs.notifyObservers(null, topic, null);
+ });
+}
+
+// Sync manages 3 broadcasters so the menus correctly reflect the Sync state.
+// Only one of these 3 should ever be visible - pass the ID of the broadcaster
+// you expect to be visible and it will check it's the only one that is.
+function checkBroadcasterVisible(broadcasterId) {
+ let all = ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"];
+ Assert.ok(all.indexOf(broadcasterId) >= 0, "valid id");
+ for (let check of all) {
+ let eltHidden = document.getElementById(check).hidden;
+ Assert.equal(eltHidden, check == broadcasterId ? false : true, check);
+ }
+}
+
+function promiseObserver(topic) {
+ return new Promise(resolve => {
+ let obs = (aSubject, aTopic, aData) => {
+ Services.obs.removeObserver(obs, aTopic);
+ resolve(aSubject);
+ }
+ Services.obs.addObserver(obs, topic, false);
+ });
+}
+
+function checkButtonTooltips(stringPrefix) {
+ for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) {
+ let text = document.getElementById(butId).getAttribute("tooltiptext");
+ let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"`
+ Assert.ok(text.startsWith(stringPrefix), desc);
+ }
+}
+
+add_task(function* prepare() {
+ // add the Sync button to the toolbar so we can get it!
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_NAVBAR);
+ registerCleanupFunction(() => {
+ CustomizableUI.removeWidgetFromArea("sync-button");
+ });
+
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ yield xps.whenLoaded();
+ // Put Sync and the UI into a known state.
+ Weave.Status.login = Weave.LOGIN_FAILED_NO_USERNAME;
+ yield notifyAndPromiseUIUpdated("weave:service:login:error");
+
+ checkBroadcasterVisible("sync-setup-state");
+ checkButtonTooltips("Sign In To Sync");
+ // mock out the "_needsSetup()" function so we don't short-circuit.
+ let oldNeedsSetup = window.gSyncUI._needsSetup;
+ window.gSyncUI._needsSetup = () => Promise.resolve(false);
+ registerCleanupFunction(() => {
+ window.gSyncUI._needsSetup = oldNeedsSetup;
+ // and an observer to set the state back to what it should be now we've
+ // restored the stub.
+ Services.obs.notifyObservers(null, "weave:service:login:finish", null);
+ });
+ // and a notification to have the state change away from "needs setup"
+ yield notifyAndPromiseUIUpdated("weave:service:login:finish");
+ checkBroadcasterVisible("sync-syncnow-state");
+ // open the sync-button panel so we can check elements in that.
+ document.getElementById("sync-button").click();
+});
+
+add_task(function* testSyncNeedsVerification() {
+ // mock out the "_needsVerification()" function
+ let oldNeedsVerification = window.gSyncUI._needsVerification;
+ window.gSyncUI._needsVerification = () => true;
+ try {
+ // a notification for the state change
+ yield notifyAndPromiseUIUpdated("weave:service:login:finish");
+ checkButtonTooltips("Verify");
+ } finally {
+ window.gSyncUI._needsVerification = oldNeedsVerification;
+ }
+});
+
+
+add_task(function* testSyncLoginError() {
+ checkBroadcasterVisible("sync-syncnow-state");
+
+ // Pretend we are in a "login failed" error state
+ Weave.Status.sync = Weave.LOGIN_FAILED;
+ Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ yield notifyAndPromiseUIUpdated("weave:ui:sync:error");
+
+ // But the menu *should* reflect the login error.
+ checkBroadcasterVisible("sync-reauth-state");
+ // The tooltips for the buttons should also reflect it.
+ checkButtonTooltips("Reconnect");
+
+ // Now pretend we just had a successful login - the error notification should go away.
+ Weave.Status.sync = Weave.STATUS_OK;
+ Weave.Status.login = Weave.LOGIN_SUCCEEDED;
+ yield notifyAndPromiseUIUpdated("weave:service:login:start");
+ yield notifyAndPromiseUIUpdated("weave:service:login:finish");
+ // The menus should be back to "all good"
+ checkBroadcasterVisible("sync-syncnow-state");
+});
+
+function checkButtonsStatus(shouldBeActive) {
+ for (let eid of [
+ "sync-status", // the broadcaster itself.
+ "sync-button", // the main sync button which observes the broadcaster
+ "PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it.
+ ]) {
+ let elt = document.getElementById(eid);
+ if (shouldBeActive) {
+ Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`);
+ } else {
+ Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`);
+ }
+ }
+}
+
+function* testButtonActions(startNotification, endNotification, expectActive = true) {
+ checkButtonsStatus(false);
+ // pretend a sync is starting.
+ yield notifyAndPromiseUIUpdated(startNotification);
+ checkButtonsStatus(expectActive);
+ // and has stopped
+ yield notifyAndPromiseUIUpdated(endNotification);
+ checkButtonsStatus(false);
+}
+
+function *doTestButtonActivities() {
+ // logins do not "activate" the spinner/button as they may block and make
+ // the UI look like Sync is never completing.
+ yield testButtonActions("weave:service:login:start", "weave:service:login:finish", false);
+ yield testButtonActions("weave:service:login:start", "weave:service:login:error", false);
+
+ // But notifications for Sync itself should activate it.
+ yield testButtonActions("weave:service:sync:start", "weave:service:sync:finish");
+ yield testButtonActions("weave:service:sync:start", "weave:service:sync:error");
+
+ // and ensure the counters correctly handle multiple in-flight syncs
+ yield notifyAndPromiseUIUpdated("weave:service:sync:start");
+ checkButtonsStatus(true);
+ // sync stops.
+ yield notifyAndPromiseUIUpdated("weave:service:sync:finish");
+ // Button should not be active.
+ checkButtonsStatus(false);
+}
+
+add_task(function* testButtonActivitiesInNavBar() {
+ // check the button's functionality while the button is in the NavBar - which
+ // it already is.
+ yield doTestButtonActivities();
+});
+
+add_task(function* testFormatLastSyncDateNow() {
+ let now = new Date();
+ let nowString = gSyncUI.formatLastSyncDate(now);
+ Assert.equal(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: 'long', hour: 'numeric', minute: 'numeric'}));
+});
+
+add_task(function* testFormatLastSyncDateMonthAgo() {
+ let monthAgo = new Date();
+ monthAgo.setMonth(monthAgo.getMonth() - 1);
+ let monthAgoString = gSyncUI.formatLastSyncDate(monthAgo);
+ Assert.equal(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: 'long', day: 'numeric'}));
+});
+
+add_task(function* testButtonActivitiesInPanel() {
+ // check the button's functionality while the button is in the panel - it's
+ // currently in the NavBar - move it to the panel and open it.
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ yield PanelUI.show();
+ try {
+ yield doTestButtonActivities();
+ } finally {
+ PanelUI.hide();
+ }
+});
diff --git a/browser/base/content/test/general/browser_tabDrop.js b/browser/base/content/test/general/browser_tabDrop.js
new file mode 100644
index 000000000..fd743e6dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabDrop.js
@@ -0,0 +1,103 @@
+registerCleanupFunction(function* cleanup() {
+ while (gBrowser.tabs.length > 1) {
+ yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+ }
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+});
+
+let originalEngine;
+add_task(function* test_setup() {
+ // Stop search-engine loads from hitting the network
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+});
+
+add_task(function*() { yield dropText("mochi.test/first", 1); });
+add_task(function*() { yield dropText("javascript:'bad'"); });
+add_task(function*() { yield dropText("jAvascript:'bad'"); });
+add_task(function*() { yield dropText("search this", 1); });
+add_task(function*() { yield dropText("mochi.test/second", 1); });
+add_task(function*() { yield dropText("data:text/html,bad"); });
+add_task(function*() { yield dropText("mochi.test/third", 1); });
+
+// Single text/plain item, with multiple links.
+add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); });
+add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 0); });
+add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 0); });
+
+// Multiple text/plain items, with single and multiple links.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/5"}],
+ [{type: "text/plain",
+ data: "mochi.test/6\nmochi.test/7"}]], 3);
+});
+
+// Single text/x-moz-url item, with multiple links.
+// "text/x-moz-url" has titles in even-numbered lines.
+add_task(function*() {
+ yield drop([[{type: "text/x-moz-url",
+ data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2);
+});
+
+// Single item with multiple types.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/10"},
+ {type: "text/x-moz-url",
+ data: "mochi.test/11\nTITLE11"}]], 1);
+});
+
+function dropText(text, expectedTabOpenCount=0) {
+ return drop([[{type: "text/plain", data: text}]], expectedTabOpenCount);
+}
+
+function* drop(dragData, expectedTabOpenCount=0) {
+ let dragDataString = JSON.stringify(dragData);
+ info(`Starting test for datagData:${dragDataString}; expectedTabOpenCount:${expectedTabOpenCount}`);
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
+ let actualTabOpenCount = 0;
+ let openedTabs = [];
+ let checkCount = function(event) {
+ openedTabs.push(event.target);
+ actualTabOpenCount++;
+ return actualTabOpenCount == expectedTabOpenCount;
+ };
+ let awaitTabOpen = expectedTabOpenCount && BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", false, checkCount);
+ // A drop type of "link" onto an existing tab would normally trigger a
+ // load in that same tab, but tabbrowser code in _getDragTargetTab treats
+ // drops on the outer edges of a tab differently (loading a new tab
+ // instead). Make events created by synthesizeDrop have all of their
+ // coordinates set to 0 (screenX/screenY), so they're treated as drops
+ // on the outer edge of the tab, thus they open new tabs.
+ var event = {
+ clientX: 0,
+ clientY: 0,
+ screenX: 0,
+ screenY: 0,
+ };
+ EventUtils.synthesizeDrop(gBrowser.selectedTab, gBrowser.selectedTab, dragData, "link", window, undefined, event);
+ let tabsOpened = false;
+ if (awaitTabOpen) {
+ yield awaitTabOpen;
+ info("Got TabOpen event");
+ tabsOpened = true;
+ for (let tab of openedTabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ }
+ is(tabsOpened, !!expectedTabOpenCount, `Tabs for ${dragDataString} should only open if any of dropped items are valid`);
+
+ yield awaitDrop;
+ ok(true, "Got drop event");
+}
diff --git a/browser/base/content/test/general/browser_tabReorder.js b/browser/base/content/test/general/browser_tabReorder.js
new file mode 100644
index 000000000..9e0503e95
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabReorder.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ let initialTabsLength = gBrowser.tabs.length;
+
+ let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true});
+ let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true});
+ let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true});
+ registerCleanupFunction(function () {
+ while (gBrowser.tabs.length > initialTabsLength) {
+ gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
+ }
+ });
+
+ is(gBrowser.tabs.length, initialTabsLength + 3, "new tabs are opened");
+ is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct");
+ is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
+ is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ function dragAndDrop(tab1, tab2, copy) {
+ let rect = tab2.getBoundingClientRect();
+ let event = {
+ ctrlKey: copy,
+ altKey: copy,
+ clientX: rect.left + rect.width / 2 + 10,
+ clientY: rect.top + rect.height / 2,
+ };
+
+ EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
+ }
+
+ dragAndDrop(newTab1, newTab2, false);
+ is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
+ is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
+ is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
+ is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
+
+ dragAndDrop(newTab2, newTab1, true);
+ is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
+ is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place");
+ is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place");
+ is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3");
+}
diff --git a/browser/base/content/test/general/browser_tab_close_dependent_window.js b/browser/base/content/test/general/browser_tab_close_dependent_window.js
new file mode 100644
index 000000000..ab8a960ac
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_close_dependent_window.js
@@ -0,0 +1,24 @@
+"use strict";
+
+add_task(function* closing_tab_with_dependents_should_close_window() {
+ info("Opening window");
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+
+ info("Opening tab with data URI");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, `data:text/html,<html%20onclick="W=window.open()"><body%20onbeforeunload="W.close()">`);
+ info("Closing original tab in this window.");
+ yield BrowserTestUtils.removeTab(win.gBrowser.tabs[0]);
+ info("Clicking into the window");
+ let depTabOpened = BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, "TabOpen");
+ yield BrowserTestUtils.synthesizeMouse("html", 0, 0, {}, tab.linkedBrowser);
+
+ let openedTab = (yield depTabOpened).target;
+ info("Got opened tab");
+
+ let windowClosedPromise = BrowserTestUtils.windowClosed(win);
+ yield BrowserTestUtils.removeTab(tab);
+ is(Cu.isDeadWrapper(openedTab) || openedTab.linkedBrowser == null, true, "Opened tab should also have closed");
+ info("If we timeout now, the window failed to close - that shouldn't happen!");
+ yield windowClosedPromise;
+});
+
diff --git a/browser/base/content/test/general/browser_tab_detach_restore.js b/browser/base/content/test/general/browser_tab_detach_restore.js
new file mode 100644
index 000000000..d482edc26
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_detach_restore.js
@@ -0,0 +1,34 @@
+"use strict";
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+add_task(function*() {
+ let uri = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+ // Clear out the closed windows set to start
+ while (SessionStore.getClosedWindowCount() > 0)
+ SessionStore.forgetClosedWindow(0);
+
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.loadURI(uri);
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+
+ let key = tab.linkedBrowser.permanentKey;
+ let win = gBrowser.replaceTabWithWindow(tab);
+ yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+
+ is(win.gBrowser.selectedBrowser.permanentKey, key, "Should have properly copied the permanentKey");
+ yield BrowserTestUtils.closeWindow(win);
+
+ is(SessionStore.getClosedWindowCount(), 1, "Should have restore data for the closed window");
+
+ win = SessionStore.undoCloseWindow(0);
+ yield BrowserTestUtils.waitForEvent(win, "load");
+ yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored");
+
+ is(win.gBrowser.tabs.length, 1, "Should have restored one tab");
+ is(win.gBrowser.selectedBrowser.currentURI.spec, uri, "Should have restored the right page");
+
+ yield promiseWindowClosed(win);
+});
diff --git a/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
new file mode 100644
index 000000000..a8fc34083
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+const EVENTUTILS_URL = "chrome://mochikit/content/tests/SimpleTest/EventUtils.js";
+var EventUtils = {};
+
+Services.scriptloader.loadSubScript(EVENTUTILS_URL, EventUtils);
+
+/**
+ * Tests that tabs from Private Browsing windows cannot be dragged
+ * into non-private windows, and vice-versa.
+ */
+add_task(function* test_dragging_private_windows() {
+ let normalWin = yield BrowserTestUtils.openNewBrowserWindow();
+ let privateWin =
+ yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ let normalTab =
+ yield BrowserTestUtils.openNewForegroundTab(normalWin.gBrowser);
+ let privateTab =
+ yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser);
+
+ let effect = EventUtils.synthesizeDrop(normalTab, privateTab,
+ [[{type: TAB_DROP_TYPE, data: normalTab}]],
+ null, normalWin, privateWin);
+ is(effect, "none", "Should not be able to drag a normal tab to a private window");
+
+ effect = EventUtils.synthesizeDrop(privateTab, normalTab,
+ [[{type: TAB_DROP_TYPE, data: privateTab}]],
+ null, privateWin, normalWin);
+ is(effect, "none", "Should not be able to drag a private tab to a normal window");
+
+ normalWin.gBrowser.swapBrowsersAndCloseOther(normalTab, privateTab);
+ is(normalWin.gBrowser.tabs.length, 2,
+ "Prevent moving a normal tab to a private tabbrowser");
+ is(privateWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a normal tab in a private tabbrowser");
+
+ privateWin.gBrowser.swapBrowsersAndCloseOther(privateTab, normalTab);
+ is(privateWin.gBrowser.tabs.length, 2,
+ "Prevent moving a private tab to a normal tabbrowser");
+ is(normalWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a private tab in a normal tabbrowser");
+
+ yield BrowserTestUtils.closeWindow(normalWin);
+ yield BrowserTestUtils.closeWindow(privateWin);
+});
+
+/**
+ * Tests that tabs from e10s windows cannot be dragged into non-e10s
+ * windows, and vice-versa.
+ */
+add_task(function* test_dragging_e10s_windows() {
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ let remoteWin = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ let nonRemoteWin = yield BrowserTestUtils.openNewBrowserWindow({remote: false});
+
+ let remoteTab =
+ yield BrowserTestUtils.openNewForegroundTab(remoteWin.gBrowser);
+ let nonRemoteTab =
+ yield BrowserTestUtils.openNewForegroundTab(nonRemoteWin.gBrowser);
+
+ let effect = EventUtils.synthesizeDrop(remoteTab, nonRemoteTab,
+ [[{type: TAB_DROP_TYPE, data: remoteTab}]],
+ null, remoteWin, nonRemoteWin);
+ is(effect, "none", "Should not be able to drag a remote tab to a non-e10s window");
+
+ effect = EventUtils.synthesizeDrop(nonRemoteTab, remoteTab,
+ [[{type: TAB_DROP_TYPE, data: nonRemoteTab}]],
+ null, nonRemoteWin, remoteWin);
+ is(effect, "none", "Should not be able to drag a non-remote tab to an e10s window");
+
+ remoteWin.gBrowser.swapBrowsersAndCloseOther(remoteTab, nonRemoteTab);
+ is(remoteWin.gBrowser.tabs.length, 2,
+ "Prevent moving a normal tab to a private tabbrowser");
+ is(nonRemoteWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a normal tab in a private tabbrowser");
+
+ nonRemoteWin.gBrowser.swapBrowsersAndCloseOther(nonRemoteTab, remoteTab);
+ is(nonRemoteWin.gBrowser.tabs.length, 2,
+ "Prevent moving a private tab to a normal tabbrowser");
+ is(remoteWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a private tab in a normal tabbrowser");
+
+ yield BrowserTestUtils.closeWindow(remoteWin);
+ yield BrowserTestUtils.closeWindow(nonRemoteWin);
+});
+
+/**
+ * Tests that remoteness-blacklisted tabs from e10s windows can
+ * be dragged between e10s windows.
+ */
+add_task(function* test_dragging_blacklisted() {
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ let remoteWin1 = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ remoteWin1.gBrowser.myID = "remoteWin1";
+ let remoteWin2 = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ remoteWin2.gBrowser.myID = "remoteWin2";
+
+ // Anything under chrome://mochitests/content/ will be blacklisted, and
+ // open in the parent process.
+ const BLACKLISTED_URL = getRootDirectory(gTestPath) +
+ "browser_tab_drag_drop_perwindow.js";
+ let blacklistedTab =
+ yield BrowserTestUtils.openNewForegroundTab(remoteWin1.gBrowser,
+ BLACKLISTED_URL);
+
+ ok(blacklistedTab.linkedBrowser, "Newly created tab should have a browser.");
+
+ ok(!blacklistedTab.linkedBrowser.isRemoteBrowser,
+ `Expected a non-remote browser for URL: ${BLACKLISTED_URL}`);
+
+ let otherTab =
+ yield BrowserTestUtils.openNewForegroundTab(remoteWin2.gBrowser);
+
+ let effect = EventUtils.synthesizeDrop(blacklistedTab, otherTab,
+ [[{type: TAB_DROP_TYPE, data: blacklistedTab}]],
+ null, remoteWin1, remoteWin2);
+ is(effect, "move", "Should be able to drag the blacklisted tab.");
+
+ // The synthesized drop should also do the work of swapping the
+ // browsers, so no need to call swapBrowsersAndCloseOther manually.
+
+ is(remoteWin1.gBrowser.tabs.length, 1,
+ "Should have moved the blacklisted tab out of this window.");
+ is(remoteWin2.gBrowser.tabs.length, 3,
+ "Should have inserted the blacklisted tab into the other window.");
+
+ // The currently selected tab in the second window should be the
+ // one we just dragged in.
+ let draggedBrowser = remoteWin2.gBrowser.selectedBrowser;
+ ok(!draggedBrowser.isRemoteBrowser,
+ "The browser we just dragged in should not be remote.");
+
+ is(draggedBrowser.currentURI.spec, BLACKLISTED_URL,
+ `Expected the URL of the dragged in tab to be ${BLACKLISTED_URL}`);
+
+ yield BrowserTestUtils.closeWindow(remoteWin1);
+ yield BrowserTestUtils.closeWindow(remoteWin2);
+});
+
+
+/**
+ * Tests that tabs dragged between windows dispatch TabOpen and TabClose
+ * events with the appropriate adoption details.
+ */
+add_task(function* test_dragging_adoption_events() {
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser);
+
+ let awaitCloseEvent = BrowserTestUtils.waitForEvent(tab1, "TabClose");
+ let awaitOpenEvent = BrowserTestUtils.waitForEvent(win2, "TabOpen");
+
+ let effect = EventUtils.synthesizeDrop(tab1, tab2,
+ [[{type: TAB_DROP_TYPE, data: tab1}]],
+ null, win1, win2);
+ is(effect, "move", "Tab should be moved from win1 to win2.");
+
+ let closeEvent = yield awaitCloseEvent;
+ let openEvent = yield awaitOpenEvent;
+
+ is(openEvent.detail.adoptedTab, tab1, "New tab adopted old tab");
+ is(closeEvent.detail.adoptedBy, openEvent.target, "Old tab adopted by new tab");
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+});
+
+
+/**
+ * Tests that per-site zoom settings remain active after a tab is
+ * dragged between windows.
+ */
+add_task(function* test_dragging_zoom_handling() {
+ const ZOOM_FACTOR = 1.62;
+
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser,
+ "http://example.com/");
+
+ win2.FullZoom.setZoom(ZOOM_FACTOR);
+ FullZoomHelper.zoomTest(tab2, ZOOM_FACTOR,
+ "Original tab should have correct zoom factor");
+
+ let effect = EventUtils.synthesizeDrop(tab2, tab1,
+ [[{type: TAB_DROP_TYPE, data: tab2}]],
+ null, win2, win1);
+ is(effect, "move", "Tab should be moved from win2 to win1.");
+
+ // Delay slightly to make sure we've finished executing any promise
+ // chains in the zoom code.
+ yield new Promise(resolve => setTimeout(resolve, 0));
+
+ FullZoomHelper.zoomTest(win1.gBrowser.selectedTab, ZOOM_FACTOR,
+ "Dragged tab should have correct zoom factor");
+
+ win1.FullZoom.reset();
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/base/content/test/general/browser_tab_dragdrop.js b/browser/base/content/test/general/browser_tab_dragdrop.js
new file mode 100644
index 000000000..cfe996e1e
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_dragdrop.js
@@ -0,0 +1,186 @@
+function swapTabsAndCloseOther(a, b) {
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
+}
+
+var getClicks = function(tab) {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function() {
+ return content.wrappedJSObject.clicks;
+ });
+}
+
+var clickTest = Task.async(function*(tab) {
+ let clicks = yield getClicks(tab);
+
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function() {
+ let target = content.document.body;
+ let rect = target.getBoundingClientRect();
+ let left = (rect.left + rect.right) / 2;
+ let top = (rect.top + rect.bottom) / 2;
+
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let newClicks = yield getClicks(tab);
+ is(newClicks, clicks + 1, "adding 1 more click on BODY");
+});
+
+function loadURI(tab, url) {
+ tab.linkedBrowser.loadURI(url);
+ return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+}
+
+// Creates a framescript which caches the current object value from the plugin
+// in the page. checkObjectValue below verifies that the framescript is still
+// active for the browser and that the cached value matches that from the plugin
+// in the page which tells us the plugin hasn't been reinitialized.
+function* cacheObjectValue(browser) {
+ yield ContentTask.spawn(browser, null, function*() {
+ let plugin = content.document.wrappedJSObject.body.firstChild;
+ info(`plugin is ${plugin}`);
+ let win = content.document.defaultView;
+ info(`win is ${win}`);
+ win.objectValue = plugin.getObjectValue();
+ info(`got objectValue: ${win.objectValue}`);
+ win.checkObjectValueListener = () => {
+ let result;
+ let exception;
+ try {
+ result = plugin.checkObjectValue(win.objectValue);
+ } catch (e) {
+ exception = e.toString();
+ }
+ info(`sending plugin.checkObjectValue(objectValue): ${result}`);
+ sendAsyncMessage("Test:CheckObjectValueResult", {
+ result,
+ exception
+ });
+ };
+
+ addMessageListener("Test:CheckObjectValue", win.checkObjectValueListener);
+ });
+}
+
+// Note, can't run this via registerCleanupFunction because it needs the
+// browser to still be alive and have a messageManager.
+function* cleanupObjectValue(browser) {
+ info("entered cleanupObjectValue")
+ yield ContentTask.spawn(browser, null, function*() {
+ info("in cleanup function");
+ let win = content.document.defaultView;
+ info(`about to delete objectValue: ${win.objectValue}`);
+ delete win.objectValue;
+ removeMessageListener("Test:CheckObjectValue", win.checkObjectValueListener);
+ info(`about to delete checkObjectValueListener: ${win.checkObjectValueListener}`);
+ delete win.checkObjectValueListener;
+ info(`deleted objectValue (${win.objectValue}) and checkObjectValueListener (${win.checkObjectValueListener})`);
+ });
+ info("exiting cleanupObjectValue")
+}
+
+// See the notes for cacheObjectValue above.
+function checkObjectValue(browser) {
+ let mm = browser.messageManager;
+
+ return new Promise((resolve, reject) => {
+ let listener = ({ data }) => {
+ mm.removeMessageListener("Test:CheckObjectValueResult", listener);
+ if (data.result === null) {
+ ok(false, "checkObjectValue threw an exception: " + data.exception);
+ reject(data.exception);
+ } else {
+ resolve(data.result);
+ }
+ };
+
+ mm.addMessageListener("Test:CheckObjectValueResult", listener);
+ mm.sendAsyncMessage("Test:CheckObjectValue");
+ });
+}
+
+add_task(function*() {
+ let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ // create a few tabs
+ let tabs = [
+ gBrowser.tabs[0],
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true})
+ ];
+
+ // Initially 0 1 2 3 4
+ yield loadURI(tabs[1], "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
+ yield loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2");
+ yield loadURI(tabs[3], "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
+ yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
+ yield BrowserTestUtils.switchTab(gBrowser, tabs[3]);
+
+ swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ is(gBrowser.tabs[2], tabs[3], "tab3");
+ is(gBrowser.tabs[3], tabs[4], "tab4");
+ delete tabs[2];
+
+ info("about to cacheObjectValue")
+ yield cacheObjectValue(tabs[4].linkedBrowser);
+ info("just finished cacheObjectValue")
+
+ swapTabsAndCloseOther(3, 2); // now: 0 1 4
+ is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 2,
+ "The third tab should be selected");
+ delete tabs[4];
+
+
+ ok((yield checkObjectValue(gBrowser.tabs[2].linkedBrowser)), "same plugin instance");
+
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ is(gBrowser.tabs[2], tabs[3], "tab4");
+
+ let clicks = yield getClicks(gBrowser.tabs[2]);
+ is(clicks, 0, "no click on BODY so far");
+ yield clickTest(gBrowser.tabs[2]);
+
+ swapTabsAndCloseOther(2, 1); // now: 0 4
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ delete tabs[3];
+
+ ok((yield checkObjectValue(gBrowser.tabs[1].linkedBrowser)), "same plugin instance");
+ yield cleanupObjectValue(gBrowser.tabs[1].linkedBrowser);
+
+ yield clickTest(gBrowser.tabs[1]);
+
+ // Load a new document (about:blank) in tab4, then detach that tab into a new window.
+ // In the new window, navigate back to the original document and click on its <body>,
+ // verify that its onclick was called.
+ is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 1,
+ "The second tab should be selected");
+ is(gBrowser.tabs[1], tabs[1],
+ "The second tab in gBrowser.tabs should be equal to the second tab in our array");
+ is(gBrowser.selectedTab, tabs[1],
+ "The second tab in our array is the selected tab");
+ yield loadURI(tabs[1], "about:blank");
+ let key = tabs[1].linkedBrowser.permanentKey;
+
+ let win = gBrowser.replaceTabWithWindow(tabs[1]);
+ yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+ delete tabs[1];
+
+ // Verify that the original window now only has the initial tab left in it.
+ is(gBrowser.tabs[0], tabs[0], "tab0");
+ is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:blank", "tab0 uri");
+
+ let tab = win.gBrowser.tabs[0];
+ is(tab.linkedBrowser.permanentKey, key, "Should have kept the key");
+
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
+ win.gBrowser.goBack();
+ yield awaitPageShow;
+
+ yield clickTest(tab);
+ promiseWindowClosed(win);
+});
diff --git a/browser/base/content/test/general/browser_tab_dragdrop2.js b/browser/base/content/test/general/browser_tab_dragdrop2.js
new file mode 100644
index 000000000..2ab622d8b
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_dragdrop2.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const ROOT = getRootDirectory(gTestPath);
+const URI = ROOT + "browser_tab_dragdrop2_frame1.xul";
+
+// Load the test page (which runs some child popup tests) in a new window.
+// After the tests were run, tear off the tab into a new window and run popup
+// tests a second time. We don't care about tests results, exceptions and
+// crashes will be caught.
+add_task(function* () {
+ // Open a new window.
+ let args = "chrome,all,dialog=no";
+ let win = window.openDialog(getBrowserURL(), "_blank", args, URI);
+
+ // Wait until the tests were run.
+ yield promiseTestsDone(win);
+ ok(true, "tests succeeded");
+
+ // Create a second tab so that we can move the original one out.
+ win.gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ // Tear off the original tab.
+ let browser = win.gBrowser.selectedBrowser;
+ let tabClosed = promiseWaitForEvent(browser, "pagehide", true);
+ let win2 = win.gBrowser.replaceTabWithWindow(win.gBrowser.tabs[0]);
+
+ // Add a 'TestsDone' event listener to ensure that the docShells is properly
+ // swapped to the new window instead of the page being loaded again. If this
+ // works fine we should *NOT* see a TestsDone event.
+ let onTestsDone = () => ok(false, "shouldn't run tests when tearing off");
+ win2.addEventListener("TestsDone", onTestsDone);
+
+ // Wait until the original tab is gone and the new window is ready.
+ yield Promise.all([tabClosed, promiseDelayedStartupFinished(win2)]);
+
+ // Remove the 'TestsDone' event listener as now
+ // we're kicking off a new test run manually.
+ win2.removeEventListener("TestsDone", onTestsDone);
+
+ // Run tests once again.
+ let promise = promiseTestsDone(win2);
+ win2.content.test_panels();
+ yield promise;
+ ok(true, "tests succeeded a second time");
+
+ // Cleanup.
+ yield promiseWindowClosed(win2);
+ yield promiseWindowClosed(win);
+});
+
+function promiseTestsDone(win) {
+ return promiseWaitForEvent(win, "TestsDone");
+}
+
+function promiseDelayedStartupFinished(win) {
+ return new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+}
diff --git a/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
new file mode 100644
index 000000000..d11709942
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
@@ -0,0 +1,169 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ XUL Widget Test for panels
+ -->
+<window title="Titlebar" width="200" height="200"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<tree id="tree" seltype="single" width="100" height="100">
+ <treecols>
+ <treecol flex="1"/>
+ <treecol flex="1"/>
+ </treecols>
+ <treechildren id="treechildren">
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ </treechildren>
+</tree>
+
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var currentTest = null;
+
+var i, waitSteps;
+var my_debug = false;
+function test_panels()
+{
+ i = waitSteps = 0;
+ checkTreeCoords();
+
+ addEventListener("popupshown", popupShown, false);
+ addEventListener("popuphidden", nextTest, false);
+ return nextTest();
+}
+
+function nextTest()
+{
+ ok(true,"popuphidden " + i)
+ if (i == tests.length) {
+ let details = {bubbles: true, cancelable: false};
+ document.dispatchEvent(new CustomEvent("TestsDone", details));
+ return i;
+ }
+
+ currentTest = tests[i];
+ var panel = createPanel(currentTest.attrs);
+ SimpleTest.waitForFocus(() => currentTest.test(panel));
+ return i;
+}
+
+function popupShown(event)
+{
+ var panel = event.target;
+ if (waitSteps > 0 && navigator.platform.indexOf("Linux") >= 0 &&
+ panel.boxObject.screenY == 210) {
+ waitSteps--;
+ setTimeout(popupShown, 10, event);
+ return;
+ }
+ ++i;
+
+ currentTest.result(currentTest.testname + " ", panel);
+ panel.hidePopup();
+}
+
+function createPanel(attrs)
+{
+ var panel = document.createElement("panel");
+ for (var a in attrs) {
+ panel.setAttribute(a, attrs[a]);
+ }
+
+ var button = document.createElement("button");
+ panel.appendChild(button);
+ button.label = "OK";
+ button.width = 120;
+ button.height = 40;
+ button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
+ panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
+ return document.documentElement.appendChild(panel);
+}
+
+function checkTreeCoords()
+{
+ var tree = $("tree");
+ var treechildren = $("treechildren");
+ tree.currentIndex = 0;
+ tree.treeBoxObject.scrollToRow(0);
+ synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { });
+
+ tree.treeBoxObject.scrollToRow(2);
+ synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { });
+}
+
+var tests = [
+ {
+ testname: "normal panel",
+ attrs: { },
+ test: function(panel) {
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+ }
+ },
+ {
+ // only noautohide panels support titlebars, so one shouldn't be shown here
+ testname: "autohide panel with titlebar",
+ attrs: { titlebar: "normal" },
+ test: function(panel) {
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+ }
+ },
+ {
+ testname: "noautohide panel with titlebar",
+ attrs: { noautohide: true, titlebar: "normal" },
+ test: function(panel) {
+ waitSteps = 25;
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+
+ var gotMouseEvent = false;
+ function mouseMoved(event)
+ {
+ gotMouseEvent = true;
+ }
+
+ panel.addEventListener("mousemove", mouseMoved, true);
+ synthesizeMouse(panel, 10, 10, { type: "mousemove" });
+ panel.removeEventListener("mousemove", mouseMoved, true);
+
+ var tree = $("tree");
+ tree.currentIndex = 0;
+ panel.appendChild(tree);
+ checkTreeCoords();
+ }
+ }
+];
+
+SimpleTest.waitForFocus(test_panels);
+
+]]>
+</script>
+
+</window>
diff --git a/browser/base/content/test/general/browser_tabbar_big_widgets.js b/browser/base/content/test/general/browser_tabbar_big_widgets.js
new file mode 100644
index 000000000..7a4c45138
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabbar_big_widgets.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const kButtonId = "test-tabbar-size-with-large-buttons";
+
+function test() {
+ registerCleanupFunction(cleanup);
+ let titlebar = document.getElementById("titlebar");
+ let originalHeight = titlebar.getBoundingClientRect().height;
+ let button = document.createElement("toolbarbutton");
+ button.id = kButtonId;
+ button.setAttribute("style", "min-height: 100px");
+ gNavToolbox.palette.appendChild(button);
+ CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_TABSTRIP);
+ let currentHeight = titlebar.getBoundingClientRect().height;
+ ok(currentHeight > originalHeight, "Titlebar should have grown");
+ CustomizableUI.removeWidgetFromArea(kButtonId);
+ currentHeight = titlebar.getBoundingClientRect().height;
+ is(currentHeight, originalHeight, "Titlebar should have gone back to its original size.");
+}
+
+function cleanup() {
+ let btn = document.getElementById(kButtonId);
+ if (btn) {
+ btn.remove();
+ }
+}
+
diff --git a/browser/base/content/test/general/browser_tabfocus.js b/browser/base/content/test/general/browser_tabfocus.js
new file mode 100644
index 000000000..4042421e8
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabfocus.js
@@ -0,0 +1,565 @@
+/*
+ * This test checks that focus is adjusted properly when switching tabs.
+ */
+
+var testPage1 = "<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>";
+var testPage2 = "<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>";
+var testPage3 = "<html id='html3'><body id='body3'><button id='button3'>Tab 3</button></body></html>";
+
+const fm = Services.focus;
+
+function EventStore() {
+ this["main-window"] = [];
+ this["window1"] = [];
+ this["window2"] = [];
+}
+
+EventStore.prototype = {
+ "push": function (event) {
+ if (event.indexOf("1") > -1) {
+ this["window1"].push(event);
+ } else if (event.indexOf("2") > -1) {
+ this["window2"].push(event);
+ } else {
+ this["main-window"].push(event);
+ }
+ }
+}
+
+var tab1 = null;
+var tab2 = null;
+var browser1 = null;
+var browser2 = null;
+var _lastfocus;
+var _lastfocuswindow = null;
+var actualEvents = new EventStore();
+var expectedEvents = new EventStore();
+var currentTestName = "";
+var _expectedElement = null;
+var _expectedWindow = null;
+
+var currentPromiseResolver = null;
+
+function* getFocusedElementForBrowser(browser, dontCheckExtraFocus = false)
+{
+ if (gMultiProcessBrowser) {
+ return new Promise((resolve, reject) => {
+ messageManager.addMessageListener("Browser:GetCurrentFocus", function getCurrentFocus(message) {
+ messageManager.removeMessageListener("Browser:GetCurrentFocus", getCurrentFocus);
+ resolve(message.data.details);
+ });
+
+ // The dontCheckExtraFocus flag is used to indicate not to check some
+ // additional focus related properties. This is needed as both URLs are
+ // loaded using the same child process and share focus managers.
+ browser.messageManager.sendAsyncMessage("Browser:GetFocusedElement",
+ { dontCheckExtraFocus : dontCheckExtraFocus });
+ });
+ }
+ var focusedWindow = {};
+ var node = fm.getFocusedElementForWindow(browser.contentWindow, false, focusedWindow);
+ return "Focus is " + (node ? node.id : "<none>");
+}
+
+function focusInChild()
+{
+ var contentFM = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+
+ function getWindowDocId(target)
+ {
+ return (String(target.location).indexOf("1") >= 0) ? "window1" : "window2";
+ }
+
+ function eventListener(event) {
+ var id;
+ if (event.target instanceof Components.interfaces.nsIDOMWindow)
+ id = getWindowDocId(event.originalTarget) + "-window";
+ else if (event.target instanceof Components.interfaces.nsIDOMDocument)
+ id = getWindowDocId(event.originalTarget) + "-document";
+ else
+ id = event.originalTarget.id;
+ sendSyncMessage("Browser:FocusChanged", { details : event.type + ": " + id });
+ }
+
+ addEventListener("focus", eventListener, true);
+ addEventListener("blur", eventListener, true);
+
+ addMessageListener("Browser:ChangeFocus", function changeFocus(message) {
+ content.document.getElementById(message.data.id)[message.data.type]();
+ });
+
+ addMessageListener("Browser:GetFocusedElement", function getFocusedElement(message) {
+ var focusedWindow = {};
+ var node = contentFM.getFocusedElementForWindow(content, false, focusedWindow);
+ var details = "Focus is " + (node ? node.id : "<none>");
+
+ /* Check focus manager properties. Add an error onto the string if they are
+ not what is expected which will cause matching to fail in the parent process. */
+ let doc = content.document;
+ if (!message.data.dontCheckExtraFocus) {
+ if (contentFM.focusedElement != node) {
+ details += "<ERROR: focusedElement doesn't match>";
+ }
+ if (contentFM.focusedWindow && contentFM.focusedWindow != content) {
+ details += "<ERROR: focusedWindow doesn't match>";
+ }
+ if ((contentFM.focusedWindow == content) != doc.hasFocus()) {
+ details += "<ERROR: child hasFocus() is not correct>";
+ }
+ if ((contentFM.focusedElement && doc.activeElement != contentFM.focusedElement) ||
+ (!contentFM.focusedElement && doc.activeElement != doc.body)) {
+ details += "<ERROR: child activeElement is not correct>";
+ }
+ }
+
+ sendSyncMessage("Browser:GetCurrentFocus", { details : details });
+ });
+}
+
+function focusElementInChild(elementid, type)
+{
+ let browser = (elementid.indexOf("1") >= 0) ? browser1 : browser2;
+ if (gMultiProcessBrowser) {
+ browser.messageManager.sendAsyncMessage("Browser:ChangeFocus",
+ { id: elementid, type: type });
+ }
+ else {
+ browser.contentDocument.getElementById(elementid)[type]();
+ }
+}
+
+add_task(function*() {
+ tab1 = gBrowser.addTab();
+ browser1 = gBrowser.getBrowserForTab(tab1);
+
+ tab2 = gBrowser.addTab();
+ browser2 = gBrowser.getBrowserForTab(tab2);
+
+ yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage1));
+ yield promiseTabLoadEvent(tab2, "data:text/html," + escape(testPage2));
+
+ var childFocusScript = "data:,(" + focusInChild.toString() + ")();";
+ browser1.messageManager.loadFrameScript(childFocusScript, true);
+ browser2.messageManager.loadFrameScript(childFocusScript, true);
+
+ gURLBar.focus();
+ yield SimpleTest.promiseFocus();
+
+ if (gMultiProcessBrowser) {
+ messageManager.addMessageListener("Browser:FocusChanged", message => {
+ actualEvents.push(message.data.details);
+ compareFocusResults();
+ });
+ }
+
+ _lastfocus = "urlbar";
+ _lastfocuswindow = "main-window";
+
+ window.addEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.addEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ // make sure that the focus initially starts out blank
+ var focusedWindow = {};
+
+ let focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is <none>", "initial focus in tab 1");
+
+ focused = yield getFocusedElementForBrowser(browser2);
+ is(focused, "Focus is <none>", "initial focus in tab 2");
+
+ is(document.activeElement, gURLBar.inputField, "focus after loading two tabs");
+
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", null, true,
+ "after tab change, focus in new tab");
+
+ focused = yield getFocusedElementForBrowser(browser2);
+ is(focused, "Focus is <none>", "focusedElement after tab change, focus in new tab");
+
+ // switching tabs when nothing in the new tab is focused
+ // should focus the browser
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true,
+ "after tab change, focus in original tab");
+
+ focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is <none>", "focusedElement after tab change, focus in original tab");
+
+ // focusing a button in the current tab should focus it
+ yield expectFocusShift(() => focusElementInChild("button1", "focus"),
+ "window1", "button1", true,
+ "after button focused");
+
+ focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is button1", "focusedElement in first browser after button focused");
+
+ // focusing a button in a background tab should not change the actual
+ // focus, but should set the focus that would be in that background tab to
+ // that button.
+ yield expectFocusShift(() => focusElementInChild("button2", "focus"),
+ "window1", "button1", false,
+ "after button focus in unfocused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is button1", "focusedElement in first browser after button focus in unfocused tab");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement in second browser after button focus in unfocused tab");
+
+ // switching tabs should now make the button in the other tab focused
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true,
+ "after tab change with button focused");
+
+ // blurring an element in a background tab should not change the active
+ // focus, but should clear the focus in that tab.
+ yield expectFocusShift(() => focusElementInChild("button1", "blur"),
+ "window2", "button2", false,
+ "focusedWindow after blur in unfocused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, true);
+ is(focused, "Focus is <none>", "focusedElement in first browser after focus in unfocused tab");
+ focused = yield getFocusedElementForBrowser(browser2, false);
+ is(focused, "Focus is button2", "focusedElement in second browser after focus in unfocused tab");
+
+ // When focus is in the tab bar, it should be retained there
+ yield expectFocusShift(() => gBrowser.selectedTab.focus(),
+ "main-window", "tab2", true,
+ "focusing tab element");
+ yield* expectFocusShiftAfterTabSwitch(tab1, "main-window", "tab1", true,
+ "tab change when selected tab element was focused");
+
+ let switchWaiter;
+ if (gMultiProcessBrowser) {
+ switchWaiter = new Promise((resolve, reject) => {
+ gBrowser.addEventListener("TabSwitchDone", function listener() {
+ gBrowser.removeEventListener("TabSwitchDone", listener);
+ executeSoon(resolve);
+ });
+ });
+ }
+
+ yield* expectFocusShiftAfterTabSwitch(tab2, "main-window", "tab2", true,
+ "another tab change when selected tab element was focused");
+
+ // When this a remote browser, wait for the paint on the second browser so that
+ // any post tab-switching stuff has time to complete before blurring the tab.
+ // Otherwise, the _adjustFocusAfterTabSwitch in tabbrowser gets confused and
+ // isn't sure what tab is really focused.
+ if (gMultiProcessBrowser) {
+ yield switchWaiter;
+ }
+
+ yield expectFocusShift(() => gBrowser.selectedTab.blur(),
+ "main-window", null, true,
+ "blurring tab element");
+
+ // focusing the url field should switch active focus away from the browser but
+ // not clear what would be the focus in the browser
+ focusElementInChild("button1", "focus");
+
+ yield expectFocusShift(() => gURLBar.focus(),
+ "main-window", "urlbar", true,
+ "focusedWindow after url field focused");
+ focused = yield getFocusedElementForBrowser(browser1, true);
+ is(focused, "Focus is button1", "focusedElement after url field focused, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement after url field focused, second browser");
+
+ yield expectFocusShift(() => gURLBar.blur(),
+ "main-window", null, true,
+ "blurring url field");
+
+ // when a chrome element is focused, switching tabs to a tab with a button
+ // with the current focus should focus the button
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", "button1", true,
+ "after tab change, focus in url field, button focused in new tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is button1", "after switch tab, focus in unfocused tab, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "after switch tab, focus in unfocused tab, second browser");
+
+ // blurring an element in the current tab should clear the active focus
+ yield expectFocusShift(() => focusElementInChild("button1", "blur"),
+ "window1", null, true,
+ "after blur in focused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is <none>", "focusedWindow after blur in focused tab, child");
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in focused tab, parent");
+
+ // blurring an non-focused url field should have no effect
+ yield expectFocusShift(() => gURLBar.blur(),
+ "window1", null, false,
+ "after blur in unfocused url field");
+
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in unfocused url field");
+
+ // switch focus to a tab with a currently focused element
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true,
+ "after switch from unfocused to focused tab");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement after switch from unfocused to focused tab");
+
+ // clearing focus on the chrome window should switch the focus to the
+ // chrome window
+ yield expectFocusShift(() => fm.clearFocus(window),
+ "main-window", null, true,
+ "after switch to chrome with no focused element");
+
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), null, "focusedElement after switch to chrome with no focused element");
+
+ // switch focus to another tab when neither have an active focus
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true,
+ "focusedWindow after tab switch from no focus to no focus");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is <none>", "after tab switch from no focus to no focus, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "after tab switch from no focus to no focus, second browser");
+
+ // next, check whether navigating forward, focusing the urlbar and then
+ // navigating back maintains the focus in the urlbar.
+ yield expectFocusShift(() => focusElementInChild("button1", "focus"),
+ "window1", "button1", true,
+ "focus button");
+
+ yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage3));
+
+ // now go back again
+ gURLBar.focus();
+
+ yield new Promise((resolve, reject) => {
+ window.addEventListener("pageshow", function navigationOccured(event) {
+ window.removeEventListener("pageshow", navigationOccured, true);
+ resolve();
+ }, true);
+ document.getElementById('Browser:Back').doCommand();
+ });
+
+ is(window.document.activeElement, gURLBar.inputField, "urlbar still focused after navigating back");
+
+ // Document navigation with F6 does not yet work in mutli-process browsers.
+ if (!gMultiProcessBrowser) {
+ gURLBar.focus();
+ actualEvents = new EventStore();
+ _lastfocus = "urlbar";
+ _lastfocuswindow = "main-window";
+
+ yield expectFocusShift(() => EventUtils.synthesizeKey("VK_F6", { }),
+ "window1", "html1",
+ true, "switch document forward with f6");
+
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6");
+
+ browser1.style.MozUserFocus = "ignore";
+ browser1.clientWidth;
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6 when browser non-focusable");
+
+ browser1.style.MozUserFocus = "normal";
+ browser1.clientWidth;
+ }
+
+ window.removeEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.removeEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+
+ finish();
+});
+
+function _browser_tabfocus_test_eventOccured(event)
+{
+ function getWindowDocId(target)
+ {
+ if (target == browser1.contentWindow || target == browser1.contentDocument) {
+ return "window1";
+ }
+ if (target == browser2.contentWindow || target == browser2.contentDocument) {
+ return "window2";
+ }
+ return "main-window";
+ }
+
+ var id;
+
+ // Some focus events from the child bubble up? Ignore them.
+ if (Cu.isCrossProcessWrapper(event.originalTarget))
+ return;
+
+ if (event.target instanceof Window)
+ id = getWindowDocId(event.originalTarget) + "-window";
+ else if (event.target instanceof Document)
+ id = getWindowDocId(event.originalTarget) + "-document";
+ else if (event.target.id == "urlbar" && event.originalTarget.localName == "input")
+ id = "urlbar";
+ else if (event.originalTarget.localName == "browser")
+ id = (event.originalTarget == browser1) ? "browser1" : "browser2";
+ else if (event.originalTarget.localName == "tab")
+ id = (event.originalTarget == tab1) ? "tab1" : "tab2";
+ else
+ id = event.originalTarget.id;
+
+ actualEvents.push(event.type + ": " + id);
+ compareFocusResults();
+}
+
+function getId(element)
+{
+ if (!element) {
+ return null;
+ }
+
+ if (element.localName == "browser") {
+ return element == browser1 ? "browser1" : "browser2";
+ }
+
+ if (element.localName == "tab") {
+ return element == tab1 ? "tab1" : "tab2";
+ }
+
+ return (element.localName == "input") ? "urlbar" : element.id;
+}
+
+function compareFocusResults()
+{
+ if (!currentPromiseResolver)
+ return;
+
+ let winIds = ["main-window", "window1", "window2"];
+
+ for (let winId of winIds) {
+ if (actualEvents[winId].length < expectedEvents[winId].length)
+ return;
+ }
+
+ for (let winId of winIds) {
+ for (let e = 0; e < expectedEvents.length; e++) {
+ is(actualEvents[winId][e], expectedEvents[winId][e], currentTestName + " events [event " + e + "]");
+ }
+ actualEvents[winId] = [];
+ }
+
+ // Use executeSoon as this will be called during a focus/blur event handler
+ executeSoon(() => {
+ let matchWindow = window;
+ if (gMultiProcessBrowser) {
+ is(_expectedWindow, "main-window", "main-window is always expected");
+ }
+ else if (_expectedWindow != "main-window") {
+ matchWindow = (_expectedWindow == "window1" ? browser1.contentWindow : browser2.contentWindow);
+ }
+
+ var focusedElement = fm.focusedElement;
+ is(getId(focusedElement), _expectedElement, currentTestName + " focusedElement");
+ is(fm.focusedWindow, matchWindow, currentTestName + " focusedWindow");
+ var focusedWindow = {};
+ is(getId(fm.getFocusedElementForWindow(matchWindow, false, focusedWindow)),
+ _expectedElement, currentTestName + " getFocusedElementForWindow");
+ is(focusedWindow.value, matchWindow, currentTestName + " getFocusedElementForWindow frame");
+ is(matchWindow.document.hasFocus(), true, currentTestName + " hasFocus");
+ var expectedActive = _expectedElement;
+ if (!expectedActive) {
+ expectedActive = matchWindow.document instanceof XULDocument ?
+ "main-window" : getId(matchWindow.document.body);
+ }
+ is(getId(matchWindow.document.activeElement), expectedActive, currentTestName + " activeElement");
+
+ currentPromiseResolver();
+ currentPromiseResolver = null;
+ });
+}
+
+function* expectFocusShiftAfterTabSwitch(tab, expectedWindow, expectedElement, focusChanged, testid)
+{
+ let tabSwitchPromise = null;
+ yield expectFocusShift(() => { tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, tab) },
+ expectedWindow, expectedElement, focusChanged, testid)
+ yield tabSwitchPromise;
+}
+
+function* expectFocusShift(callback, expectedWindow, expectedElement, focusChanged, testid)
+{
+ currentPromiseResolver = null;
+ currentTestName = testid;
+
+ expectedEvents = new EventStore();
+
+ if (focusChanged) {
+ _expectedElement = expectedElement;
+ _expectedWindow = expectedWindow;
+
+ // When the content is in a child process, the expected element in the chrome window
+ // will always be the urlbar or a browser element.
+ if (gMultiProcessBrowser) {
+ if (_expectedWindow == "window1") {
+ _expectedElement = "browser1";
+ }
+ else if (_expectedWindow == "window2") {
+ _expectedElement = "browser2";
+ }
+ _expectedWindow = "main-window";
+ }
+
+ if (gMultiProcessBrowser && _lastfocuswindow != "main-window" &&
+ _lastfocuswindow != expectedWindow) {
+ let browserid = _lastfocuswindow == "window1" ? "browser1" : "browser2";
+ expectedEvents.push("blur: " + browserid);
+ }
+
+ var newElementIsFocused = (expectedElement && !expectedElement.startsWith("html"));
+ if (newElementIsFocused && gMultiProcessBrowser &&
+ _lastfocuswindow != "main-window" &&
+ expectedWindow == "main-window") {
+ // When switching from a child to a chrome element, the focus on the element will arrive first.
+ expectedEvents.push("focus: " + expectedElement);
+ newElementIsFocused = false;
+ }
+
+ if (_lastfocus && _lastfocus != _expectedElement)
+ expectedEvents.push("blur: " + _lastfocus);
+
+ if (_lastfocuswindow &&
+ _lastfocuswindow != expectedWindow) {
+
+ if (!gMultiProcessBrowser || _lastfocuswindow != "main-window") {
+ expectedEvents.push("blur: " + _lastfocuswindow + "-document");
+ expectedEvents.push("blur: " + _lastfocuswindow + "-window");
+ }
+ }
+
+ if (expectedWindow && _lastfocuswindow != expectedWindow) {
+ if (gMultiProcessBrowser && expectedWindow != "main-window") {
+ let browserid = expectedWindow == "window1" ? "browser1" : "browser2";
+ expectedEvents.push("focus: " + browserid);
+ }
+
+ if (!gMultiProcessBrowser || expectedWindow != "main-window") {
+ expectedEvents.push("focus: " + expectedWindow + "-document");
+ expectedEvents.push("focus: " + expectedWindow + "-window");
+ }
+ }
+
+ if (newElementIsFocused) {
+ expectedEvents.push("focus: " + expectedElement);
+ }
+
+ _lastfocus = expectedElement;
+ _lastfocuswindow = expectedWindow;
+ }
+
+ return new Promise((resolve, reject) => {
+ currentPromiseResolver = resolve;
+ callback();
+
+ // No events are expected, so resolve the promise immediately.
+ if (expectedEvents["main-window"].length + expectedEvents["window1"].length + expectedEvents["window2"].length == 0) {
+ currentPromiseResolver();
+ currentPromiseResolver = null;
+ }
+ });
+}
diff --git a/browser/base/content/test/general/browser_tabkeynavigation.js b/browser/base/content/test/general/browser_tabkeynavigation.js
new file mode 100644
index 000000000..d8e51f4b9
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabkeynavigation.js
@@ -0,0 +1,156 @@
+/*
+ * This test checks that keyboard navigation for tabs isn't blocked by content
+ */
+add_task(function* test() {
+
+ let testPage1 = "data:text/html,<html id='tab1'><body><button id='button1'>Tab 1</button></body></html>";
+ let testPage2 = "data:text/html,<html id='tab2'><body><button id='button2'>Tab 2</button><script>function preventDefault(event) { event.preventDefault(); event.stopImmediatePropagation(); } window.addEventListener('keydown', preventDefault, true); window.addEventListener('keypress', preventDefault, true);</script></body></html>";
+ let testPage3 = "data:text/html,<html id='tab3'><body><button id='button3'>Tab 3</button></body></html>";
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1);
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2);
+ let tab3 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3);
+
+ // Kill the animation for simpler test.
+ Services.prefs.setBoolPref("browser.tabs.animate", false);
+
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+Tab on Tab1");
+
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+Tab on Tab2");
+
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+Shift+Tab on Tab3");
+
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+Shift+Tab on Tab2");
+
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+PageDown on Tab1");
+
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+PageDown on Tab2");
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+PageUp on Tab3");
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+PageUp on Tab2");
+
+ if (gBrowser.mTabBox._handleMetaAltArrows) {
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ let ltr = window.getComputedStyle(gBrowser.mTabBox, "").direction == "ltr";
+ let advanceKey = ltr ? "VK_RIGHT" : "VK_LEFT";
+ let reverseKey = ltr ? "VK_LEFT" : "VK_RIGHT";
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+ EventUtils.synthesizeKey(advanceKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + advanceKey + " on Tab1");
+
+ EventUtils.synthesizeKey(advanceKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+" + advanceKey + " on Tab2");
+
+ EventUtils.synthesizeKey(reverseKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + reverseKey + " on Tab3");
+
+ EventUtils.synthesizeKey(reverseKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+" + reverseKey + " on Tab2");
+ }
+
+ gBrowser.selectedTab = tab2;
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated");
+ is(gBrowser.tabContainer.selectedIndex, 2,
+ "Tab2 index should be 2");
+
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated after Ctrl+Shift+PageDown");
+ is(gBrowser.tabContainer.selectedIndex, 3,
+ "Tab2 index should be 1 after Ctrl+Shift+PageDown");
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated after Ctrl+Shift+PageUp");
+ is(gBrowser.tabContainer.selectedIndex, 2,
+ "Tab2 index should be 2 after Ctrl+Shift+PageUp");
+
+ if (navigator.platform.indexOf("Mac") == 0) {
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ // XXX Currently, Command + "{" and "}" don't work if keydown event is
+ // consumed because following keypress event isn't fired.
+
+ let ltr = window.getComputedStyle(gBrowser.mTabBox, "").direction == "ltr";
+ let advanceKey = ltr ? "}" : "{";
+ let reverseKey = ltr ? "{" : "}";
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+
+ EventUtils.synthesizeKey(advanceKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + advanceKey + " on Tab1");
+
+ EventUtils.synthesizeKey(advanceKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+" + advanceKey + " on Tab2");
+
+ EventUtils.synthesizeKey(reverseKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + reverseKey + " on Tab3");
+
+ EventUtils.synthesizeKey(reverseKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+" + reverseKey + " on Tab2");
+ } else {
+ gBrowser.selectedTab = tab2;
+ EventUtils.synthesizeKey("VK_F4", { type: "keydown", ctrlKey: true });
+
+ isnot(gBrowser.selectedTab, tab2,
+ "Tab2 should be closed by pressing Ctrl+F4 on Tab2");
+ is(gBrowser.tabs.length, 3,
+ "The count of tabs should be 3 since tab2 should be closed");
+
+ // NOTE: keypress event shouldn't be fired since the keydown event should
+ // be consumed by tab2.
+ EventUtils.synthesizeKey("VK_F4", { type: "keyup", ctrlKey: true });
+ is(gBrowser.tabs.length, 3,
+ "The count of tabs should be 3 since renaming key events shouldn't close other tabs");
+ }
+
+ gBrowser.selectedTab = tab3;
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+
+ Services.prefs.clearUserPref("browser.tabs.animate");
+});
diff --git a/browser/base/content/test/general/browser_tabopen_reflows.js b/browser/base/content/test/general/browser_tabopen_reflows.js
new file mode 100644
index 000000000..8e04cf12e
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -0,0 +1,157 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyGetter(this, "docShell", () => {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+});
+
+const EXPECTED_REFLOWS = [
+ // tabbrowser.adjustTabstrip() call after tabopen animation has finished
+ "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" +
+ "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+ "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+ // switching focus in updateCurrentBrowser() causes reflows
+ "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml|" +
+ "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
+ "onselect@chrome://browser/content/browser.xul|",
+
+ // switching focus in openLinkIn() causes reflows
+ "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "BrowserOpenTab@chrome://browser/content/browser.js|",
+
+ // accessing element.scrollPosition in _fillTrailingGap() flushes layout
+ "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
+ "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
+ "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+ "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+ // SessionStore.getWindowDimensions()
+ "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",
+
+ // selection change notification may cause querying the focused editor content
+ // by IME and that will cause reflow.
+ "select@chrome://global/content/bindings/textbox.xml|" +
+ "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
+ "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "BrowserOpenTab@chrome://browser/content/browser.js|",
+
+];
+
+const PREF_PRELOAD = "browser.newtab.preload";
+const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening new tabs.
+ */
+add_task(function*() {
+ let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
+ let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
+ let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+
+ // resolves promise when directory links are downloaded and written to disk
+ function watchLinksChangeOnce() {
+ let deferred = Promise.defer();
+ let observer = {
+ onManyLinksChanged: () => {
+ DirectoryLinksProvider.removeObserver(observer);
+ NewTabUtils.links.populateCache(() => {
+ NewTabUtils.allPages.update();
+ deferred.resolve();
+ }, true);
+ }
+ };
+ observer.onDownloadFail = observer.onManyLinksChanged;
+ DirectoryLinksProvider.addObserver(observer);
+ return deferred.promise;
+ }
+
+ let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_PRELOAD);
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
+ return watchLinksChangeOnce();
+ });
+
+ Services.prefs.setBoolPref(PREF_PRELOAD, false);
+ // set directory source to dummy/empty links
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}');
+
+ // run tests when directory source change completes
+ yield watchLinksChangeOnce();
+
+ // Perform a click in the top left of content to ensure the mouse isn't
+ // hovering over any of the tiles
+ let target = gBrowser.selectedBrowser;
+ let rect = target.getBoundingClientRect();
+ let left = rect.left + 1;
+ let top = rect.top + 1;
+
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+
+ // Add a reflow observer and open a new tab.
+ docShell.addWeakReflowObserver(observer);
+ BrowserOpenTab();
+
+ // Wait until the tabopen animation has finished.
+ yield waitForTransitionEnd();
+
+ // Remove reflow observer and clean up.
+ docShell.removeWeakReflowObserver(observer);
+ gBrowser.removeCurrentTab();
+});
+
+var observer = {
+ reflow: function (start, end) {
+ // Gather information about the current code path.
+ let path = (new Error().stack).split("\n").slice(1).map(line => {
+ return line.replace(/:\d+:\d+$/, "");
+ }).join("|");
+ let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
+
+ // Stack trace is empty. Reflow was triggered by native code.
+ if (path === "") {
+ return;
+ }
+
+ // Check if this is an expected reflow.
+ for (let stack of EXPECTED_REFLOWS) {
+ if (path.startsWith(stack)) {
+ ok(true, "expected uninterruptible reflow '" + stack + "'");
+ return;
+ }
+ }
+
+ ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
+ },
+
+ reflowInterruptible: function (start, end) {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function waitForTransitionEnd() {
+ return new Promise(resolve => {
+ let tab = gBrowser.selectedTab;
+ tab.addEventListener("transitionend", function onEnd(event) {
+ if (event.propertyName === "max-width") {
+ tab.removeEventListener("transitionend", onEnd);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_tabs_close_beforeunload.js b/browser/base/content/test/general/browser_tabs_close_beforeunload.js
new file mode 100644
index 000000000..b867efd72
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabs_close_beforeunload.js
@@ -0,0 +1,49 @@
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+const FIRST_TAB = getRootDirectory(gTestPath) + "close_beforeunload_opens_second_tab.html";
+const SECOND_TAB = getRootDirectory(gTestPath) + "close_beforeunload.html";
+
+add_task(function*() {
+ info("Opening first tab");
+ let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, FIRST_TAB);
+ let secondTabLoadedPromise;
+ let secondTab;
+ let tabOpened = new Promise(resolve => {
+ info("Adding tabopen listener");
+ gBrowser.tabContainer.addEventListener("TabOpen", function tabOpenListener(e) {
+ info("Got tabopen, removing listener and waiting for load");
+ gBrowser.tabContainer.removeEventListener("TabOpen", tabOpenListener, false, false);
+ secondTab = e.target;
+ secondTabLoadedPromise = BrowserTestUtils.browserLoaded(secondTab.linkedBrowser, false, SECOND_TAB);
+ resolve();
+ }, false, false);
+ });
+ info("Opening second tab using a click");
+ yield ContentTask.spawn(firstTab.linkedBrowser, "", function*() {
+ content.document.getElementsByTagName("a")[0].click();
+ });
+ info("Waiting for the second tab to be opened");
+ yield tabOpened;
+ info("Waiting for the load in that tab to finish");
+ yield secondTabLoadedPromise;
+
+ let closeBtn = document.getAnonymousElementByAttribute(secondTab, "anonid", "close-button");
+ let closePromise = BrowserTestUtils.removeTab(secondTab, {dontRemove: true});
+ info("closing second tab (which will self-close in beforeunload)");
+ closeBtn.click();
+ ok(secondTab.closing, "Second tab should be marked as closing synchronously.");
+ yield closePromise;
+ ok(secondTab.closing, "Second tab should still be marked as closing");
+ ok(!secondTab.linkedBrowser, "Second tab's browser should be dead");
+ ok(!firstTab.closing, "First tab should not be closing");
+ ok(firstTab.linkedBrowser, "First tab's browser should be alive");
+ info("closing first tab");
+ yield BrowserTestUtils.removeTab(firstTab);
+
+ ok(firstTab.closing, "First tab should be marked as closing");
+ ok(!firstTab.linkedBrowser, "First tab's browser should be dead");
+});
diff --git a/browser/base/content/test/general/browser_tabs_isActive.js b/browser/base/content/test/general/browser_tabs_isActive.js
new file mode 100644
index 000000000..0725757e7
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabs_isActive.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for the docshell active state of local and remote browsers.
+
+const kTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+ executeSoon(resolve);
+ });
+ });
+}
+
+function getParentTabState(aTab) {
+ return aTab.linkedBrowser.docShellIsActive;
+}
+
+function getChildTabState(aTab) {
+ return ContentTask.spawn(aTab.linkedBrowser, {}, function* () {
+ return docShell.isActive;
+ });
+}
+
+function checkState(parentSide, childSide, value, message) {
+ is(parentSide, value, message + " (parent side)");
+ is(childSide, value, message + " (child side)");
+}
+
+function waitForMs(aMs) {
+ return new Promise((resolve) => {
+ setTimeout(done, aMs);
+ function done() {
+ resolve(true);
+ }
+ });
+}
+
+add_task(function *() {
+ let url = kTestPage;
+ let originalTab = gBrowser.selectedTab; // test tab
+ let newTab = gBrowser.addTab(url, {skipAnimation: true});
+ let parentSide, childSide;
+
+ // new tab added but not selected checks
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active initially");
+
+ // select the newly added tab and wait for TabSwitchDone event
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ ok(newTab.linkedBrowser.isRemoteBrowser, "for testing we need a remote tab");
+ }
+
+ // check active state of both tabs
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is not active while unselected");
+
+ // switch back to the original test tab and wait for TabSwitchDone event
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = originalTab;
+ yield tabSwitchedPromise;
+
+ // check active state of both tabs
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active again after switch back");
+
+ // switch to the new tab and wait for TabSwitchDone event
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ // check active state of both tabs
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is active again after switch back");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function *() {
+ let url = "about:about";
+ let originalTab = gBrowser.selectedTab; // test tab
+ let newTab = gBrowser.addTab(url, {skipAnimation: true});
+ let parentSide, childSide;
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active initially");
+
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ ok(!newTab.linkedBrowser.isRemoteBrowser, "for testing we need a local tab");
+ }
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is not active while unselected");
+
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = originalTab;
+ yield tabSwitchedPromise;
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active again after switch back");
+
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is active again after switch back");
+
+ gBrowser.removeTab(newTab);
+});
diff --git a/browser/base/content/test/general/browser_tabs_owner.js b/browser/base/content/test/general/browser_tabs_owner.js
new file mode 100644
index 000000000..300d783ba
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabs_owner.js
@@ -0,0 +1,44 @@
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: gBrowser._finalizeTabSwitch is not a function");
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: gBrowser._finalizeTabSwitch is not a function");
+
+function test() {
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ var tabs = gBrowser.tabs;
+ var owner;
+
+ is(tabs.length, 4, "4 tabs are open");
+
+ owner = gBrowser.selectedTab = tabs[2];
+ BrowserOpenTab();
+ is(gBrowser.selectedTab, tabs[4], "newly opened tab is selected");
+ gBrowser.removeCurrentTab();
+ is(gBrowser.selectedTab, owner, "owner is selected");
+
+ owner = gBrowser.selectedTab;
+ BrowserOpenTab();
+ gBrowser.selectedTab = tabs[1];
+ gBrowser.selectedTab = tabs[4];
+ gBrowser.removeCurrentTab();
+ isnot(gBrowser.selectedTab, owner, "selecting a different tab clears the owner relation");
+
+ owner = gBrowser.selectedTab;
+ BrowserOpenTab();
+ gBrowser.moveTabTo(gBrowser.selectedTab, 0);
+ gBrowser.removeCurrentTab();
+ is(gBrowser.selectedTab, owner, "owner relatitionship persists when tab is moved");
+
+ while (tabs.length > 1)
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js
new file mode 100644
index 000000000..f90f047d3
--- /dev/null
+++ b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js
@@ -0,0 +1,126 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const OPEN_LOCATION_PREF = "browser.link.open_newwindow";
+const NON_REMOTE_PAGE = "about:welcomeback";
+
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+requestLongerTimeout(2);
+
+function frame_script() {
+ content.document.body.innerHTML = `
+ <a href="about:home" target="_blank" id="testAnchor">Open a window</a>
+ `;
+
+ let element = content.document.getElementById("testAnchor");
+ element.click();
+}
+
+/**
+ * Takes some browser in some window, and forces that browser
+ * to become non-remote, and then navigates it to a page that
+ * we're not supposed to be displaying remotely. Returns a
+ * Promise that resolves when the browser is no longer remote.
+ */
+function prepareNonRemoteBrowser(aWindow, browser) {
+ browser.loadURI(NON_REMOTE_PAGE);
+ return BrowserTestUtils.browserLoaded(browser);
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(OPEN_LOCATION_PREF);
+});
+
+/**
+ * Test that if we open a new tab from a link in a non-remote
+ * browser in an e10s window, that the new tab will load properly.
+ */
+add_task(function* test_new_tab() {
+ let normalWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true,
+ });
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true,
+ private: true,
+ });
+
+ for (let testWindow of [normalWindow, privateWindow]) {
+ yield promiseWaitForFocus(testWindow);
+ let testBrowser = testWindow.gBrowser.selectedBrowser;
+ info("Preparing non-remote browser");
+ yield prepareNonRemoteBrowser(testWindow, testBrowser);
+ info("Non-remote browser prepared - sending frame script");
+
+ // Get our framescript ready
+ let mm = testBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+
+ let tabOpenEvent = yield waitForNewTabEvent(testWindow.gBrowser);
+ let newTab = tabOpenEvent.target;
+
+ yield promiseTabLoadEvent(newTab);
+
+ // Our framescript opens to about:home which means that the
+ // tab should eventually become remote.
+ ok(newTab.linkedBrowser.isRemoteBrowser,
+ "The opened browser never became remote.");
+
+ testWindow.gBrowser.removeTab(newTab);
+ }
+
+ normalWindow.close();
+ privateWindow.close();
+});
+
+/**
+ * Test that if we open a new window from a link in a non-remote
+ * browser in an e10s window, that the new window is not an e10s
+ * window. Also tests with a private browsing window.
+ */
+add_task(function* test_new_window() {
+ let normalWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true
+ }, true);
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true,
+ private: true,
+ }, true);
+
+ // Fiddle with the prefs so that we open target="_blank" links
+ // in new windows instead of new tabs.
+ Services.prefs.setIntPref(OPEN_LOCATION_PREF,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW);
+
+ for (let testWindow of [normalWindow, privateWindow]) {
+ yield promiseWaitForFocus(testWindow);
+ let testBrowser = testWindow.gBrowser.selectedBrowser;
+ yield prepareNonRemoteBrowser(testWindow, testBrowser);
+
+ // Get our framescript ready
+ let mm = testBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+
+ // Click on the link in the browser, and wait for the new window.
+ let {subject: newWindow} =
+ yield promiseTopicObserved("browser-delayed-startup-finished");
+
+ is(PrivateBrowsingUtils.isWindowPrivate(testWindow),
+ PrivateBrowsingUtils.isWindowPrivate(newWindow),
+ "Private browsing state of new window does not match the original!");
+
+ let newTab = newWindow.gBrowser.selectedTab;
+
+ yield promiseTabLoadEvent(newTab);
+
+ // Our framescript opens to about:home which means that the
+ // tab should eventually become remote.
+ ok(newTab.linkedBrowser.isRemoteBrowser,
+ "The opened browser never became remote.");
+ newWindow.close();
+ }
+
+ normalWindow.close();
+ privateWindow.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_1.js b/browser/base/content/test/general/browser_trackingUI_1.js
new file mode 100644
index 000000000..937d607af
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_1.js
@@ -0,0 +1,170 @@
+/*
+ * Test that the Tracking Protection section is visible in the Control Center
+ * and has the correct state for the cases when:
+ * 1) A page with no tracking elements is loaded.
+ * 2) A page with tracking elements is loaded and they are blocked.
+ * 3) A page with tracking elements is loaded and they are not blocked.
+ * See also Bugs 1175327, 1043801, 1178985
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var tabbrowser = null;
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = tabbrowser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function hidden(sel) {
+ let win = tabbrowser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null);
+ return display === "none" || opacity === "0";
+}
+
+function clickButton(sel) {
+ let win = tabbrowser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ el.doCommand();
+}
+
+function testBenignPage() {
+ info("Non-tracking content must not be blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ ok(!TrackingProtection.content.hasAttribute("state"), "content: no state");
+ ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
+ ok(!TrackingProtection.icon.hasAttribute("tooltiptext"), "icon: no tooltip");
+
+ ok(hidden("#tracking-protection-icon"), "icon is hidden");
+ ok(hidden("#tracking-action-block"), "blockButton is hidden");
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+ // Make sure that the no tracking elements message appears
+ ok(!hidden("#tracking-not-detected"), "labelNoTracking is visible");
+ ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+ ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+function testTrackingPage(window) {
+ info("Tracking content must be blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+ 'content: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+ 'icon: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("tooltiptext"),
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(hidden("#tracking-action-block"), "blockButton is hidden");
+
+
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+ ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+ } else {
+ ok(!hidden("#tracking-action-unblock"), "unblockButton is visible");
+ ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden");
+ }
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+ ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+}
+
+function testTrackingPageUnblocked() {
+ info("Tracking content must be white-listed and not blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
+ 'content: state="loaded-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
+ 'icon: state="loaded-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("tooltiptext"),
+ gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(!hidden("#tracking-action-block"), "blockButton is visible");
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible");
+ ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+function* testTrackingProtectionForTab(tab) {
+ info("Load a test page not containing tracking elements");
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ testBenignPage();
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-unblock");
+ yield tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ info("Re-enable TP for the page (which reloads the page)");
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-block");
+ yield tabReloadPromise;
+ testTrackingPage(tab.ownerGlobal);
+}
+
+add_task(function* testNormalBrowsing() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ tabbrowser = gBrowser;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF),
+ "TP.enabled is based on the original pref value");
+
+ Services.prefs.setBoolPref(PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionForTab(tab);
+
+ Services.prefs.setBoolPref(PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ tabbrowser = privateWin.gBrowser;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF),
+ "TP.enabled is based on the pb pref value");
+
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionForTab(tab);
+
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_2.js b/browser/base/content/test/general/browser_trackingUI_2.js
new file mode 100644
index 000000000..96ccb6c2e
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_2.js
@@ -0,0 +1,96 @@
+/*
+ * Test that the Tracking Protection section is never visible in the
+ * Control Center when the feature is off.
+ * See also Bugs 1175327, 1043801, 1178985.
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var tabbrowser = null;
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = tabbrowser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function hidden(el) {
+ let win = el.ownerGlobal;
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null);
+
+ return display === "none" || opacity === "0";
+}
+
+add_task(function* testNormalBrowsing() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ tabbrowser = gBrowser;
+ let {gIdentityHandler} = tabbrowser.ownerGlobal;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF),
+ "TP.enabled is based on the original pref value");
+
+ Services.prefs.setBoolPref(PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ Services.prefs.setBoolPref(PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ gIdentityHandler._identityBox.click();
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+
+ info("Load a test page not containing tracking elements");
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ gIdentityHandler._identityBox.click();
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ tabbrowser = privateWin.gBrowser;
+ let {gIdentityHandler} = tabbrowser.ownerGlobal;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF),
+ "TP.enabled is based on the pb pref value");
+
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ gIdentityHandler._identityBox.click();
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+
+ info("Load a test page not containing tracking elements");
+ gIdentityHandler._identityBox.click();
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_3.js b/browser/base/content/test/general/browser_trackingUI_3.js
new file mode 100644
index 000000000..63f8a13bc
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_3.js
@@ -0,0 +1,52 @@
+/*
+ * Test that the Tracking Protection is correctly enabled / disabled
+ * in both normal and private windows given all possible states of the prefs:
+ * privacy.trackingprotection.enabled
+ * privacy.trackingprotection.pbmode.enabled
+ * See also Bug 1178985.
+ */
+
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+});
+
+add_task(function* testNormalBrowsing() {
+ let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ Services.prefs.setBoolPref(PREF, true);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)");
+
+ Services.prefs.setBoolPref(PREF, false);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=true)");
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ let TrackingProtection = privateWin.gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ Services.prefs.setBoolPref(PREF, true);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)");
+
+ Services.prefs.setBoolPref(PREF, false);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=false,PB=true)");
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_4.js b/browser/base/content/test/general/browser_trackingUI_4.js
new file mode 100644
index 000000000..93a06913e
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_4.js
@@ -0,0 +1,109 @@
+/*
+ * Test that the Tracking Protection icon is properly animated in the identity
+ * block when loading tabs and switching between tabs.
+ * See also Bug 1175858.
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var tabbrowser = null;
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = tabbrowser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function waitForSecurityChange(numChanges = 1) {
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onSecurityChange: function() {
+ n = n + 1;
+ info ("Received onSecurityChange event " + n + " of " + numChanges);
+ if (n >= numChanges) {
+ tabbrowser.removeProgressListener(listener);
+ resolve();
+ }
+ }
+ };
+ tabbrowser.addProgressListener(listener);
+ });
+}
+
+function* testTrackingProtectionAnimation() {
+ info("Load a test page not containing tracking elements");
+ let benignTab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
+
+ ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
+ ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
+
+ info("Load a test page containing tracking elements");
+ let trackingTab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser, TRACKING_PAGE);
+
+ ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
+ ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
+
+ info("Switch from tracking -> benign tab");
+ let securityChanged = waitForSecurityChange();
+ tabbrowser.selectedTab = benignTab;
+ yield securityChanged;
+
+ ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
+ ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate");
+
+ info("Switch from benign -> tracking tab");
+ securityChanged = waitForSecurityChange();
+ tabbrowser.selectedTab = trackingTab;
+ yield securityChanged;
+
+ ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
+ ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate");
+
+ info("Reload tracking tab");
+ securityChanged = waitForSecurityChange(2);
+ tabbrowser.reload();
+ yield securityChanged;
+
+ ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
+ ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
+}
+
+add_task(function* testNormalBrowsing() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ tabbrowser = gBrowser;
+
+ TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ Services.prefs.setBoolPref(PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionAnimation();
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ tabbrowser = privateWin.gBrowser;
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionAnimation();
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_5.js b/browser/base/content/test/general/browser_trackingUI_5.js
new file mode 100644
index 000000000..23164a5b2
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_5.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that sites added to the Tracking Protection whitelist in private
+// browsing mode don't persist once the private browsing window closes.
+
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var browser = null;
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = browser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+function hidden(sel) {
+ let win = browser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ return display === "none";
+}
+
+function identityPopupState() {
+ let win = browser.ownerGlobal;
+ return win.document.getElementById("identity-popup").state;
+}
+
+function clickButton(sel) {
+ let win = browser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ el.doCommand();
+}
+
+function testTrackingPage(window) {
+ info("Tracking content must be blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+ 'content: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+ 'icon: state="blocked-tracking-content"');
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(hidden("#tracking-action-block"), "blockButton is hidden");
+
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+ ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+ ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+}
+
+function testTrackingPageUnblocked() {
+ info("Tracking content must be white-listed and not blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
+ 'content: state="loaded-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
+ 'icon: state="loaded-tracking-content"');
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(!hidden("#tracking-action-block"), "blockButton is visible");
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible");
+ ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+add_task(function* testExceptionAddition() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ browser = privateWin.gBrowser;
+ let tab = browser.selectedTab = browser.addTab();
+
+ TrackingProtection = browser.ownerGlobal.TrackingProtection;
+ yield pushPrefs([PB_PREF, true]);
+
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-unblock");
+ is(identityPopupState(), "closed", "foobar");
+
+ yield tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ info("Test that the exception is remembered across tabs in the same private window");
+ tab = browser.selectedTab = browser.addTab();
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ testTrackingPageUnblocked();
+
+ yield promiseWindowClosed(privateWin);
+});
+
+add_task(function* testExceptionPersistence() {
+ info("Open another private browsing window");
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ browser = privateWin.gBrowser;
+ let tab = browser.selectedTab = browser.addTab();
+
+ TrackingProtection = browser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection.enabled, "TP is still enabled");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-unblock");
+ is(identityPopupState(), "closed", "foobar");
+
+ yield tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_6.js b/browser/base/content/test/general/browser_trackingUI_6.js
new file mode 100644
index 000000000..be91bc4a0
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_6.js
@@ -0,0 +1,46 @@
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_trackingUI_6.html";
+
+function waitForSecurityChange(numChanges = 1) {
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onSecurityChange: function() {
+ n = n + 1;
+ info ("Received onSecurityChange event " + n + " of " + numChanges);
+ if (n >= numChanges) {
+ gBrowser.removeProgressListener(listener);
+ resolve();
+ }
+ }
+ };
+ gBrowser.addProgressListener(listener);
+ });
+}
+
+add_task(function* test_fetch() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({ set: [['privacy.trackingprotection.enabled', true]] },
+ resolve);
+ });
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, function* (newTabBrowser) {
+ let securityChange = waitForSecurityChange();
+ yield ContentTask.spawn(newTabBrowser, null, function* () {
+ yield content.wrappedJSObject.test_fetch()
+ .then(response => Assert.ok(false, "should have denied the request"))
+ .catch(e => Assert.ok(true, `Caught exception: ${e}`));
+ });
+ yield securityChange;
+
+ var TrackingProtection = newTabBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "got TP object");
+ ok(TrackingProtection.enabled, "TP is enabled");
+
+ is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+ 'content: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+ 'icon: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("tooltiptext"),
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
+ });
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_telemetry.js b/browser/base/content/test/general/browser_trackingUI_telemetry.js
new file mode 100644
index 000000000..d9fce18d4
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_telemetry.js
@@ -0,0 +1,145 @@
+/*
+ * Test telemetry for Tracking Protection
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+/**
+ * Enable local telemetry recording for the duration of the tests.
+ */
+var oldCanRecord = Services.telemetry.canRecordExtended;
+Services.telemetry.canRecordExtended = true;
+Services.prefs.setBoolPref(PREF, false);
+Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").clear();
+registerCleanupFunction(function () {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.prefs.clearUserPref(PREF);
+});
+
+function getShieldHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD");
+}
+
+function getEnabledHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED");
+}
+
+function getEventsHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
+}
+
+function getShieldCounts() {
+ return getShieldHistogram().snapshot().counts;
+}
+
+function getEnabledCounts() {
+ return getEnabledHistogram().snapshot().counts;
+}
+
+function getEventCounts() {
+ return getEventsHistogram().snapshot().counts;
+}
+
+add_task(function* setup() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ ok(!TrackingProtection.enabled, "TP is not enabled");
+
+ // Open a window with TP disabled to make sure 'enabled' is logged correctly.
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ yield promiseWindowClosed(newWin);
+
+ is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
+ is(getEnabledCounts()[1], 0, "TP was not enabled on start up");
+
+ // Enable TP so the next browser to open will log 'enabled'
+ Services.prefs.setBoolPref(PREF, true);
+});
+
+
+add_task(function* testNewWindow() {
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let tab = newWin.gBrowser.selectedTab = newWin.gBrowser.addTab();
+ let TrackingProtection = newWin.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
+ is(getEnabledCounts()[1], 1, "TP was enabled once on start up");
+
+ // Reset these to make counting easier
+ getEventsHistogram().clear();
+ getShieldHistogram().clear();
+
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ is(getEventCounts()[0], 1, "Total page loads");
+ is(getEventCounts()[1], 0, "Disable actions");
+ is(getEventCounts()[2], 0, "Enable actions");
+ is(getShieldCounts()[0], 1, "Page loads without tracking");
+
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ // Note that right now the events and shield histogram is not measuring what
+ // you might think. Since onSecurityChange fires twice for a tracking page,
+ // the total page loads count is double counting, and the shield count
+ // (which is meant to measure times when the shield wasn't shown) fires even
+ // when tracking elements exist on the page.
+ todo_is(getEventCounts()[0], 2, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+ is(getEventCounts()[1], 0, "Disable actions");
+ is(getEventCounts()[2], 0, "Enable actions");
+ todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ newWin.document.querySelector("#tracking-action-unblock").doCommand();
+ yield tabReloadPromise;
+ todo_is(getEventCounts()[0], 3, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+ is(getEventCounts()[1], 1, "Disable actions");
+ is(getEventCounts()[2], 0, "Enable actions");
+ todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+ info("Re-enable TP for the page (which reloads the page)");
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ newWin.document.querySelector("#tracking-action-block").doCommand();
+ yield tabReloadPromise;
+ todo_is(getEventCounts()[0], 4, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+ is(getEventCounts()[1], 1, "Disable actions");
+ is(getEventCounts()[2], 1, "Enable actions");
+ todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+ yield promiseWindowClosed(newWin);
+
+ // Reset these to make counting easier for the next test
+ getEventsHistogram().clear();
+ getShieldHistogram().clear();
+ getEnabledHistogram().clear();
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ let tab = privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab();
+ let TrackingProtection = privateWin.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ // Do a bunch of actions and make sure that no telemetry data is gathered
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ privateWin.document.querySelector("#tracking-action-unblock").doCommand();
+ yield tabReloadPromise;
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ privateWin.document.querySelector("#tracking-action-block").doCommand();
+ yield tabReloadPromise;
+
+ // Sum up all the counts to make sure that nothing got logged
+ is(getEnabledCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode");
+ is(getEventCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode");
+ is(getShieldCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode");
+
+ yield promiseWindowClosed(privateWin);
+});
diff --git a/browser/base/content/test/general/browser_typeAheadFind.js b/browser/base/content/test/general/browser_typeAheadFind.js
new file mode 100644
index 000000000..1d550944a
--- /dev/null
+++ b/browser/base/content/test/general/browser_typeAheadFind.js
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function *() {
+ let testWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ testWindow.gBrowser.loadURI("data:text/html,<h1>A Page</h1>");
+ yield BrowserTestUtils.browserLoaded(testWindow.gBrowser.selectedBrowser);
+
+ yield SimpleTest.promiseFocus(testWindow.gBrowser.selectedBrowser);
+
+ ok(!testWindow.gFindBarInitialized, "find bar is not initialized");
+
+ let findBarOpenPromise = promiseWaitForEvent(testWindow.gBrowser, "findbaropen");
+ EventUtils.synthesizeKey("/", {}, testWindow);
+ yield findBarOpenPromise;
+
+ ok(testWindow.gFindBarInitialized, "find bar is now initialized");
+
+ yield BrowserTestUtils.closeWindow(testWindow);
+});
diff --git a/browser/base/content/test/general/browser_unknownContentType_title.js b/browser/base/content/test/general/browser_unknownContentType_title.js
new file mode 100644
index 000000000..269406bdb
--- /dev/null
+++ b/browser/base/content/test/general/browser_unknownContentType_title.js
@@ -0,0 +1,33 @@
+const url = "data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3Ctitle%3ETest%20Page%3C%2Ftitle%3E%3C%2Fhead%3E%3C%2Fhtml%3E";
+const unknown_url = "http://example.com/browser/browser/base/content/test/general/unknownContentType_file.pif";
+
+function waitForNewWindow() {
+ return new Promise(resolve => {
+ let listener = (win) => {
+ Services.obs.removeObserver(listener, "toplevel-window-ready");
+ win.addEventListener("load", () => {
+ resolve(win);
+ });
+ };
+
+ Services.obs.addObserver(listener, "toplevel-window-ready", false)
+ });
+}
+
+add_task(function*() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = tab.linkedBrowser;
+ yield promiseTabLoaded(gBrowser.selectedTab);
+
+ is(gBrowser.contentTitle, "Test Page", "Should have the right title.")
+
+ browser.loadURI(unknown_url);
+ let win = yield waitForNewWindow();
+ is(win.location, "chrome://mozapps/content/downloads/unknownContentType.xul",
+ "Should have seen the unknown content dialog.");
+ is(gBrowser.contentTitle, "Test Page", "Should still have the right title.")
+
+ win.close();
+ yield promiseWaitForFocus(window);
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_unloaddialogs.js b/browser/base/content/test/general/browser_unloaddialogs.js
new file mode 100644
index 000000000..bf3790b95
--- /dev/null
+++ b/browser/base/content/test/general/browser_unloaddialogs.js
@@ -0,0 +1,41 @@
+var testUrls =
+ [
+ "data:text/html,<script>" +
+ "function handle(evt) {" +
+ "evt.target.removeEventListener(evt.type, handle, true);" +
+ "try { alert('This should NOT appear'); } catch(e) { }" +
+ "}" +
+ "window.addEventListener('pagehide', handle, true);" +
+ "window.addEventListener('beforeunload', handle, true);" +
+ "window.addEventListener('unload', handle, true);" +
+ "</script><body>Testing alert during pagehide/beforeunload/unload</body>",
+ "data:text/html,<script>" +
+ "function handle(evt) {" +
+ "evt.target.removeEventListener(evt.type, handle, true);" +
+ "try { prompt('This should NOT appear'); } catch(e) { }" +
+ "}" +
+ "window.addEventListener('pagehide', handle, true);" +
+ "window.addEventListener('beforeunload', handle, true);" +
+ "window.addEventListener('unload', handle, true);" +
+ "</script><body>Testing prompt during pagehide/beforeunload/unload</body>",
+ "data:text/html,<script>" +
+ "function handle(evt) {" +
+ "evt.target.removeEventListener(evt.type, handle, true);" +
+ "try { confirm('This should NOT appear'); } catch(e) { }" +
+ "}" +
+ "window.addEventListener('pagehide', handle, true);" +
+ "window.addEventListener('beforeunload', handle, true);" +
+ "window.addEventListener('unload', handle, true);" +
+ "</script><body>Testing confirm during pagehide/beforeunload/unload</body>",
+ ];
+
+add_task(function*() {
+ for (let url of testUrls) {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ ok(true, "Loaded page " + url);
+ // Wait one turn of the event loop before closing, so everything settles.
+ yield new Promise(resolve => setTimeout(resolve, 0));
+ yield BrowserTestUtils.removeTab(tab);
+ ok(true, "Closed page " + url + " without timeout");
+ }
+});
diff --git a/browser/base/content/test/general/browser_utilityOverlay.js b/browser/base/content/test/general/browser_utilityOverlay.js
new file mode 100644
index 000000000..34adc00d9
--- /dev/null
+++ b/browser/base/content/test/general/browser_utilityOverlay.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const gTests = [
+ test_eventMatchesKey,
+ test_getTopWin,
+ test_getBoolPref,
+ test_openNewTabWith,
+ test_openUILink
+];
+
+function test () {
+ waitForExplicitFinish();
+ executeSoon(runNextTest);
+}
+
+function runNextTest() {
+ if (gTests.length) {
+ let testFun = gTests.shift();
+ info("Running " + testFun.name);
+ testFun()
+ }
+ else {
+ finish();
+ }
+}
+
+function test_eventMatchesKey() {
+ let eventMatchResult;
+ let key;
+ let checkEvent = function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ eventMatchResult = eventMatchesKey(e, key);
+ }
+ document.addEventListener("keypress", checkEvent);
+
+ try {
+ key = document.createElement("key");
+ let keyset = document.getElementById("mainKeyset");
+ key.setAttribute("key", "t");
+ key.setAttribute("modifiers", "accel");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("t", {accelKey: true});
+ is(eventMatchResult, true, "eventMatchesKey: one modifier");
+ keyset.removeChild(key);
+
+ key = document.createElement("key");
+ key.setAttribute("key", "g");
+ key.setAttribute("modifiers", "accel,shift");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("g", {accelKey: true, shiftKey: true});
+ is(eventMatchResult, true, "eventMatchesKey: combination modifiers");
+ keyset.removeChild(key);
+
+ key = document.createElement("key");
+ key.setAttribute("key", "w");
+ key.setAttribute("modifiers", "accel");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("f", {accelKey: true});
+ is(eventMatchResult, false, "eventMatchesKey: mismatch keys");
+ keyset.removeChild(key);
+
+ key = document.createElement("key");
+ key.setAttribute("keycode", "VK_DELETE");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("VK_DELETE", {accelKey: true});
+ is(eventMatchResult, false, "eventMatchesKey: mismatch modifiers");
+ keyset.removeChild(key);
+ } finally {
+ // Make sure to remove the event listener so future tests don't
+ // fail when they simulate key presses.
+ document.removeEventListener("keypress", checkEvent);
+ }
+
+ runNextTest();
+}
+
+function test_getTopWin() {
+ is(getTopWin(), window, "got top window");
+ runNextTest();
+}
+
+
+function test_getBoolPref() {
+ is(getBoolPref("browser.search.openintab", false), false, "getBoolPref");
+ is(getBoolPref("this.pref.doesnt.exist", true), true, "getBoolPref fallback");
+ is(getBoolPref("this.pref.doesnt.exist", false), false, "getBoolPref fallback #2");
+ runNextTest();
+}
+
+function test_openNewTabWith() {
+ openNewTabWith("http://example.com/");
+ let tab = gBrowser.selectedTab = gBrowser.tabs[1];
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ is(tab.linkedBrowser.currentURI.spec, "http://example.com/", "example.com loaded");
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ });
+}
+
+function test_openUILink() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ is(tab.linkedBrowser.currentURI.spec, "http://example.org/", "example.org loaded");
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ });
+
+ openUILink("http://example.org/"); // defaults to "current"
+}
diff --git a/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js
new file mode 100644
index 000000000..c8f3cdc96
--- /dev/null
+++ b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js
@@ -0,0 +1,55 @@
+function wait_while_tab_is_busy() {
+ return new Promise(resolve => {
+ let progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ gBrowser.removeProgressListener(this);
+ setTimeout(resolve, 0);
+ }
+ }
+ };
+ gBrowser.addProgressListener(progressListener);
+ });
+}
+
+// This function waits for the tab to stop being busy instead of waiting for it
+// to load, since the canViewSource change happens at that time.
+var with_new_tab_opened = Task.async(function* (options, taskFn) {
+ let busyPromise = wait_while_tab_is_busy();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(options.gBrowser, options.url, false);
+ yield busyPromise;
+ yield taskFn(tab.linkedBrowser);
+ gBrowser.removeTab(tab);
+});
+
+add_task(function*() {
+ yield new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["view_source.tab", true],
+ ]}, resolve);
+ });
+});
+
+add_task(function* test_regular_page() {
+ function* test_expect_view_source_enabled(browser) {
+ ok(!XULBrowserWindow.canViewSource.hasAttribute("disabled"),
+ "View Source should be enabled");
+ }
+
+ yield with_new_tab_opened({
+ gBrowser,
+ url: "http://example.com",
+ }, test_expect_view_source_enabled);
+});
+
+add_task(function* test_view_source_page() {
+ function* test_expect_view_source_disabled(browser) {
+ ok(XULBrowserWindow.canViewSource.hasAttribute("disabled"),
+ "View Source should be disabled");
+ }
+
+ yield with_new_tab_opened({
+ gBrowser,
+ url: "view-source:http://example.com",
+ }, test_expect_view_source_disabled);
+});
diff --git a/browser/base/content/test/general/browser_visibleFindSelection.js b/browser/base/content/test/general/browser_visibleFindSelection.js
new file mode 100644
index 000000000..630490644
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleFindSelection.js
@@ -0,0 +1,52 @@
+add_task(function*() {
+ const childContent = "<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>" +
+ "div</div><div style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'>" +
+ "<span id='s'>div</span></div>";
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(childContent));
+ yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW);
+
+ let findBarOpenPromise = promiseWaitForEvent(gBrowser, "findbaropen");
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ yield findBarOpenPromise;
+
+ ok(gFindBarInitialized, "find bar is now initialized");
+
+ // Finds the div in the green box.
+ let scrollPromise = promiseWaitForEvent(gBrowser, "scroll");
+ EventUtils.synthesizeKey("d", {});
+ EventUtils.synthesizeKey("i", {});
+ EventUtils.synthesizeKey("v", {});
+ yield scrollPromise;
+
+ // Wait for one paint to ensure we've processed the previous key events and scrolling.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ return new Promise(
+ resolve => {
+ content.requestAnimationFrame(() => {
+ setTimeout(resolve, 0);
+ });
+ }
+ );
+ });
+
+ // Finds the div in the red box.
+ scrollPromise = promiseWaitForEvent(gBrowser, "scroll");
+ EventUtils.synthesizeKey("g", { accelKey: true });
+ yield scrollPromise;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ Assert.ok(content.document.getElementById("s").getBoundingClientRect().left >= 0,
+ "scroll should include find result");
+ });
+
+ // clear the find bar
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ EventUtils.synthesizeKey("VK_DELETE", { });
+
+ gFindBar.close();
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_visibleTabs.js b/browser/base/content/test/general/browser_visibleTabs.js
new file mode 100644
index 000000000..e9130bc18
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(function* () {
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+
+ // Add a tab that will get pinned
+ let pinned = gBrowser.addTab();
+ gBrowser.pinTab(pinned);
+
+ let testTab = gBrowser.addTab();
+
+ let visible = gBrowser.visibleTabs;
+ is(visible.length, 3, "3 tabs should be open");
+ is(visible[0], pinned, "the pinned tab is first");
+ is(visible[1], origTab, "original tab is next");
+ is(visible[2], testTab, "last created tab is last");
+
+ // Only show the test tab (but also get pinned and selected)
+ is(gBrowser.selectedTab, origTab, "sanity check that we're on the original tab");
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.visibleTabs.length, 3, "all 3 tabs are still visible");
+
+ // Select the test tab and only show that (and pinned)
+ gBrowser.selectedTab = testTab;
+ gBrowser.showOnlyTheseTabs([testTab]);
+
+ visible = gBrowser.visibleTabs;
+ is(visible.length, 2, "2 tabs should be visible including the pinned");
+ is(visible[0], pinned, "first is pinned");
+ is(visible[1], testTab, "next is the test tab");
+ is(gBrowser.tabs.length, 3, "3 tabs should still be open");
+
+ gBrowser.selectTabAtIndex(1);
+ is(gBrowser.selectedTab, testTab, "second tab is the test tab");
+ gBrowser.selectTabAtIndex(0);
+ is(gBrowser.selectedTab, pinned, "first tab is pinned");
+ gBrowser.selectTabAtIndex(2);
+ is(gBrowser.selectedTab, testTab, "no third tab, so no change");
+ gBrowser.selectTabAtIndex(0);
+ is(gBrowser.selectedTab, pinned, "switch back to the pinned");
+ gBrowser.selectTabAtIndex(2);
+ is(gBrowser.selectedTab, testTab, "no third tab, so select last tab");
+ gBrowser.selectTabAtIndex(-2);
+ is(gBrowser.selectedTab, pinned, "pinned tab is second from left (when orig tab is hidden)");
+ gBrowser.selectTabAtIndex(-1);
+ is(gBrowser.selectedTab, testTab, "last tab is the test tab");
+
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, pinned, "wrapped around the end to pinned");
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, testTab, "next to test tab");
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, pinned, "next to pinned again");
+
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, testTab, "going backwards to last tab");
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, pinned, "next to pinned");
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, testTab, "next to test tab again");
+
+ // Try showing all tabs
+ gBrowser.showOnlyTheseTabs(Array.slice(gBrowser.tabs));
+ is(gBrowser.visibleTabs.length, 3, "all 3 tabs are visible again");
+
+ // Select the pinned tab and show the testTab to make sure selection updates
+ gBrowser.selectedTab = pinned;
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.tabs[1], origTab, "make sure origTab is in the middle");
+ is(origTab.hidden, true, "make sure it's hidden");
+ gBrowser.removeTab(pinned);
+ is(gBrowser.selectedTab, testTab, "making sure origTab was skipped");
+ is(gBrowser.visibleTabs.length, 1, "only testTab is there");
+
+ // Only show one of the non-pinned tabs (but testTab is selected)
+ gBrowser.showOnlyTheseTabs([origTab]);
+ is(gBrowser.visibleTabs.length, 2, "got 2 tabs");
+
+ // Now really only show one of the tabs
+ gBrowser.showOnlyTheseTabs([testTab]);
+ visible = gBrowser.visibleTabs;
+ is(visible.length, 1, "only the original tab is visible");
+ is(visible[0], testTab, "it's the original tab");
+ is(gBrowser.tabs.length, 2, "still have 2 open tabs");
+
+ // Close the last visible tab and make sure we still get a visible tab
+ gBrowser.removeTab(testTab);
+ is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
+ is(gBrowser.tabs.length, 1, "sanity check that it matches");
+ is(gBrowser.selectedTab, origTab, "got the orig tab");
+ is(origTab.hidden, false, "and it's not hidden -- visible!");
+});
diff --git a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
new file mode 100644
index 000000000..827f86c05
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let tabOne = gBrowser.addTab("about:blank");
+ let tabTwo = gBrowser.addTab("http://mochi.test:8888/");
+ gBrowser.selectedTab = tabTwo;
+
+ let browser = gBrowser.getBrowserForTab(tabTwo);
+ let onLoad = function() {
+ browser.removeEventListener("load", onLoad, true);
+
+ gBrowser.showOnlyTheseTabs([tabTwo]);
+
+ is(gBrowser.visibleTabs.length, 1, "Only one tab is visible");
+
+ let uris = PlacesCommandHook.uniqueCurrentPages;
+ is(uris.length, 1, "Only one uri is returned");
+
+ is(uris[0].uri.spec, tabTwo.linkedBrowser.currentURI.spec, "It's the correct URI");
+
+ gBrowser.removeTab(tabOne);
+ gBrowser.removeTab(tabTwo);
+ Array.forEach(gBrowser.tabs, function(tab) {
+ gBrowser.showTab(tab);
+ });
+
+ finish();
+ }
+ browser.addEventListener("load", onLoad, true);
+}
diff --git a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
new file mode 100644
index 000000000..0a0ea87bd
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ is(gBrowser.visibleTabs.length, 1, "1 tab should be open");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled");
+
+ // Add a tab
+ let testTab1 = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "2 tabs should be open");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled since there are two tabs with the same address");
+
+ let testTab2 = gBrowser.addTab("about:mozilla");
+ is(gBrowser.visibleTabs.length, 3, "3 tabs should be open");
+ // Wait for tab load, the code checks for currentURI.
+ testTab2.linkedBrowser.addEventListener("load", function () {
+ testTab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ is(Disabled(), false, "Bookmark All Tabs should be enabled since there are two tabs with different addresses");
+
+ // Hide the original tab
+ gBrowser.selectedTab = testTab2;
+ gBrowser.showOnlyTheseTabs([testTab2]);
+ is(gBrowser.visibleTabs.length, 1, "1 tab should be visible");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled as there is only one visible tab");
+
+ // Add a tab that will get pinned
+ let pinned = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "2 tabs should be visible now");
+ is(Disabled(), false, "Bookmark All Tabs should be available as there are two visible tabs");
+ gBrowser.pinTab(pinned);
+ is(Hidden(), false, "Bookmark All Tabs should be visible on a normal tab");
+ is(Disabled(), true, "Bookmark All Tabs should not be available since one tab is pinned");
+ gBrowser.selectedTab = pinned;
+ is(Hidden(), true, "Bookmark All Tabs should be hidden on a pinned tab");
+
+ // Show all tabs
+ let allTabs = Array.from(gBrowser.tabs);
+ gBrowser.showOnlyTheseTabs(allTabs);
+
+ // reset the environment
+ gBrowser.removeTab(testTab2);
+ gBrowser.removeTab(testTab1);
+ gBrowser.removeTab(pinned);
+ is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
+ is(gBrowser.tabs.length, 1, "sanity check that it matches");
+ is(Disabled(), true, "Bookmark All Tabs should be hidden");
+ is(gBrowser.selectedTab, origTab, "got the orig tab");
+ is(origTab.hidden, false, "and it's not hidden -- visible!");
+ finish();
+ }, true);
+}
+
+function Disabled() {
+ updateTabContextMenu();
+ return document.getElementById("Browser:BookmarkAllTabs").getAttribute("disabled") == "true";
+}
+
+function Hidden() {
+ updateTabContextMenu();
+ return document.getElementById("context_bookmarkAllTabs").hidden;
+}
diff --git a/browser/base/content/test/general/browser_visibleTabs_contextMenu.js b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
new file mode 100644
index 000000000..4fdab3d8a
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+
+add_task(function* test() {
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
+ let testTab = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs");
+
+ // Check the context menu with two tabs
+ updateTabContextMenu(origTab);
+ is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled");
+ is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");
+
+
+ if (gFxAccounts.sendTabToDeviceEnabled) {
+ // Check the send tab to device menu item
+ const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
+ yield updateTabContextMenu(origTab, function* () {
+ yield openMenuItemSubmenu("context_sendTabToDevice");
+ });
+ is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
+ let targets = document.getElementById("context_sendTabToDevicePopupMenu").childNodes;
+ is(targets[0].getAttribute("label"), "Foo", "Foo target is present");
+ is(targets[1].getAttribute("label"), "Bar", "Bar target is present");
+ is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present");
+ restoreRemoteClients(oldGetter);
+ }
+
+ // Hide the original tab.
+ gBrowser.selectedTab = testTab;
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.visibleTabs.length, 1, "now there is only one visible tab");
+
+ // Check the context menu with one tab.
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled when more than one tab exists");
+ is(document.getElementById("context_reloadAllTabs").disabled, true, "Reload All Tabs is disabled");
+
+ // Add a tab that will get pinned
+ // So now there's one pinned tab, one visible unpinned tab, and one hidden tab
+ let pinned = gBrowser.addTab();
+ gBrowser.pinTab(pinned);
+ is(gBrowser.visibleTabs.length, 2, "now there are two visible tabs");
+
+ // Check the context menu on the unpinned visible tab
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeOtherTabs").disabled, true, "Close Other Tabs is disabled");
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled");
+
+ // Show all tabs
+ let allTabs = Array.from(gBrowser.tabs);
+ gBrowser.showOnlyTheseTabs(allTabs);
+
+ // Check the context menu now
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeOtherTabs").disabled, false, "Close Other Tabs is enabled");
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled");
+
+ // Check the context menu of the original tab
+ // Close Tabs To The End should now be enabled
+ updateTabContextMenu(origTab);
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, false, "Close Tabs To The End is enabled");
+
+ gBrowser.removeTab(testTab);
+ gBrowser.removeTab(pinned);
+});
+
diff --git a/browser/base/content/test/general/browser_visibleTabs_tabPreview.js b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
new file mode 100644
index 000000000..7ce4b143f
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test() {
+ gPrefService.setBoolPref("browser.ctrlTab.previews", true);
+
+ let [origTab] = gBrowser.visibleTabs;
+ let tabOne = gBrowser.addTab();
+ let tabTwo = gBrowser.addTab();
+
+ // test the ctrlTab.tabList
+ pressCtrlTab();
+ ok(ctrlTab.tabList.length, 3, "Show 3 tabs in tab preview");
+ releaseCtrl();
+
+ gBrowser.showOnlyTheseTabs([origTab]);
+ pressCtrlTab();
+ ok(ctrlTab.tabList.length, 1, "Show 1 tab in tab preview");
+ ok(!ctrlTab.isOpen, "With 1 tab open, Ctrl+Tab doesn't open the preview panel");
+
+ gBrowser.showOnlyTheseTabs([origTab, tabOne, tabTwo]);
+ pressCtrlTab();
+ ok(ctrlTab.isOpen, "With 3 tabs open, Ctrl+Tab does open the preview panel");
+ releaseCtrl();
+
+ // cleanup
+ gBrowser.removeTab(tabOne);
+ gBrowser.removeTab(tabTwo);
+
+ if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
+ gPrefService.clearUserPref("browser.ctrlTab.previews");
+});
+
+function pressCtrlTab(aShiftKey) {
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
+}
+
+function releaseCtrl() {
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
+}
diff --git a/browser/base/content/test/general/browser_web_channel.html b/browser/base/content/test/general/browser_web_channel.html
new file mode 100644
index 000000000..f117ccca2
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel.html
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>web_channel_test</title>
+</head>
+<body>
+<script>
+ var IFRAME_SRC_ROOT = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
+
+ window.onload = function() {
+ var testName = window.location.search.replace(/^\?/, "");
+
+ switch (testName) {
+ case "generic":
+ test_generic();
+ break;
+ case "twoway":
+ test_twoWay();
+ break;
+ case "multichannel":
+ test_multichannel();
+ break;
+ case "iframe":
+ test_iframe();
+ break;
+ case "iframe_pre_redirect":
+ test_iframe_pre_redirect();
+ break;
+ case "unsolicited":
+ test_unsolicited();
+ break;
+ case "bubbles":
+ test_bubbles();
+ break;
+ case "object":
+ test_object();
+ break;
+ default:
+ throw new Error(`INVALID TEST NAME ${testName}`);
+ }
+ };
+
+ function test_generic() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "generic",
+ message: {
+ something: {
+ nested: "hello",
+ },
+ }
+ })
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_twoWay() {
+ var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "one",
+ },
+ })
+ });
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "two",
+ detail: e.detail.message,
+ },
+ }),
+ });
+
+ if (!e.detail.message.error) {
+ window.dispatchEvent(secondMessage);
+ }
+ }, true);
+
+ window.dispatchEvent(firstMessage);
+ }
+
+ function test_multichannel() {
+ var event1 = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "wrongchannel",
+ message: {},
+ })
+ });
+
+ var event2 = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "multichannel",
+ message: {},
+ })
+ });
+
+ window.dispatchEvent(event1);
+ window.dispatchEvent(event2);
+ }
+
+ function test_iframe() {
+ // Note that this message is the response to the message sent
+ // by the iframe! This is bad, as this page is *not* trusted.
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ // the test parent will fail if the echo message is received.
+ echoEventToChannel(e, "echo");
+ });
+
+ // only attach the iframe for the iframe test to avoid
+ // interfering with other tests.
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe");
+ document.body.appendChild(iframe);
+ }
+
+ function test_iframe_pre_redirect() {
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe_pre_redirect");
+ document.body.appendChild(iframe);
+ }
+
+ function test_unsolicited() {
+ // echo any unsolicted events back to chrome.
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ echoEventToChannel(e, "echo");
+ }, true);
+ }
+
+ function test_bubbles() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "not_a_window",
+ message: {
+ command: "start"
+ }
+ })
+ });
+
+ var nonWindowTarget = document.getElementById("not_a_window");
+
+ nonWindowTarget.addEventListener("WebChannelMessageToContent", function(e) {
+ echoEventToChannel(e, "not_a_window");
+ }, true);
+
+
+ nonWindowTarget.dispatchEvent(event);
+ }
+
+ function test_object() {
+ let objectMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "objects",
+ message: { type: "object" }
+ }
+ });
+
+ let stringMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "objects",
+ message: { type: "string" }
+ })
+ });
+ // Test fails if objectMessage is received, we send stringMessage to know
+ // when we should stop listening for objectMessage
+ window.dispatchEvent(objectMessage);
+ window.dispatchEvent(stringMessage);
+ }
+
+ function echoEventToChannel(e, channelId) {
+ var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: channelId,
+ message: e.detail.message,
+ })
+ });
+
+ e.target.dispatchEvent(echoedEvent);
+ }
+</script>
+
+<div id="not_a_window"></div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_web_channel.js b/browser/base/content/test/general/browser_web_channel.js
new file mode 100644
index 000000000..abc1c6fef
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -0,0 +1,436 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+ "resource://gre/modules/WebChannel.jsm");
+
+const HTTP_PATH = "http://example.com";
+const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html";
+const HTTP_MISMATCH_PATH = "http://example.org";
+const HTTP_IFRAME_PATH = "http://mochi.test:8888";
+const HTTP_REDIRECTED_IFRAME_PATH = "http://example.org";
+
+requestLongerTimeout(2); // timeouts in debug builds.
+
+// Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js
+// as much as possible. (We only have that since we can't run browser chrome
+// tests on Android. Yet?)
+var gTests = [
+ {
+ desc: "WebChannel generic message",
+ run: function* () {
+ return new Promise(function(resolve, reject) {
+ let tab;
+ let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
+ channel.listen(function (id, message, target) {
+ is(id, "generic");
+ is(message.something.nested, "hello");
+ channel.stopListening();
+ gBrowser.removeTab(tab);
+ resolve();
+ });
+
+ tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?generic");
+ });
+ }
+ },
+ {
+ desc: "WebChannel generic message in a private window.",
+ run: function* () {
+ let promiseTestDone = new Promise(function(resolve, reject) {
+ let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
+ channel.listen(function(id, message, target) {
+ is(id, "generic");
+ is(message.something.nested, "hello");
+ channel.stopListening();
+ resolve();
+ });
+ });
+
+ const url = HTTP_PATH + HTTP_ENDPOINT + "?generic";
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, url);
+ yield promiseTestDone;
+ yield BrowserTestUtils.closeWindow(privateWindow);
+ }
+ },
+ {
+ desc: "WebChannel two way communication",
+ run: function* () {
+ return new Promise(function(resolve, reject) {
+ let tab;
+ let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH, null, null));
+
+ channel.listen(function (id, message, sender) {
+ is(id, "twoway", "bad id");
+ ok(message.command, "command not ok");
+
+ if (message.command === "one") {
+ channel.send({ data: { nested: true } }, sender);
+ }
+
+ if (message.command === "two") {
+ is(message.detail.data.nested, true);
+ channel.stopListening();
+ gBrowser.removeTab(tab);
+ resolve();
+ }
+ });
+
+ tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?twoway");
+ });
+ }
+ },
+ {
+ desc: "WebChannel two way communication in an iframe",
+ run: function* () {
+ let parentChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+ let iframeChannel = new WebChannel("twoway", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
+ let promiseTestDone = new Promise(function (resolve, reject) {
+ parentChannel.listen(function (id, message, sender) {
+ reject(new Error("WebChannel message incorrectly sent to parent"));
+ });
+
+ iframeChannel.listen(function (id, message, sender) {
+ is(id, "twoway", "bad id (2)");
+ ok(message.command, "command not ok (2)");
+
+ if (message.command === "one") {
+ iframeChannel.send({ data: { nested: true } }, sender);
+ }
+
+ if (message.command === "two") {
+ is(message.detail.data.nested, true);
+ resolve();
+ }
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?iframe"
+ }, function* () {
+ yield promiseTestDone;
+ parentChannel.stopListening();
+ iframeChannel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel response to a redirected iframe",
+ run: function* () {
+ /**
+ * This test checks that WebChannel responses are only sent
+ * to an iframe if the iframe has not redirected to another origin.
+ * Test flow:
+ * 1. create a page, embed an iframe on origin A.
+ * 2. the iframe sends a message `redirecting`, then redirects to
+ * origin B.
+ * 3. the iframe at origin B is set up to echo any messages back to the
+ * test parent.
+ * 4. the test parent receives the `redirecting` message from origin A.
+ * the test parent creates a new channel with origin B.
+ * 5. when origin B is ready, it sends a `loaded` message to the test
+ * parent, letting the test parent know origin B is ready to echo
+ * messages.
+ * 5. the test parent tries to send a response to origin A. If the
+ * WebChannel does not perform a valid origin check, the response
+ * will be received by origin B. If the WebChannel does perform
+ * a valid origin check, the response will not be sent.
+ * 6. the test parent sends a `done` message to origin B, which origin
+ * B echoes back. If the response to origin A is not echoed but
+ * the message to origin B is, then hooray, the test passes.
+ */
+
+ let preRedirectChannel = new WebChannel("pre_redirect", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
+ let postRedirectChannel = new WebChannel("post_redirect", Services.io.newURI(HTTP_REDIRECTED_IFRAME_PATH, null, null));
+
+ let promiseTestDone = new Promise(function (resolve, reject) {
+ preRedirectChannel.listen(function (id, message, preRedirectSender) {
+ if (message.command === "redirecting") {
+
+ postRedirectChannel.listen(function (aId, aMessage, aPostRedirectSender) {
+ is(aId, "post_redirect");
+ isnot(aMessage.command, "no_response_expected");
+
+ if (aMessage.command === "loaded") {
+ // The message should not be received on the preRedirectChannel
+ // because the target window has redirected.
+ preRedirectChannel.send({ command: "no_response_expected" }, preRedirectSender);
+ postRedirectChannel.send({ command: "done" }, aPostRedirectSender);
+ } else if (aMessage.command === "done") {
+ resolve();
+ } else {
+ reject(new Error(`Unexpected command ${aMessage.command}`));
+ }
+ });
+ } else {
+ reject(new Error(`Unexpected command ${message.command}`));
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?iframe_pre_redirect"
+ }, function* () {
+ yield promiseTestDone;
+ preRedirectChannel.stopListening();
+ postRedirectChannel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel multichannel",
+ run: function* () {
+ return new Promise(function(resolve, reject) {
+ let tab;
+ let channel = new WebChannel("multichannel", Services.io.newURI(HTTP_PATH, null, null));
+
+ channel.listen(function (id, message, sender) {
+ is(id, "multichannel");
+ gBrowser.removeTab(tab);
+ resolve();
+ });
+
+ tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel");
+ });
+ }
+ },
+ {
+ desc: "WebChannel unsolicited send, using system principal",
+ run: function* () {
+ let channel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+ // an unsolicted message is sent from Chrome->Content which is then
+ // echoed back. If the echo is received here, then the content
+ // received the message.
+ let messagePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ is(id, "echo");
+ is(message.command, "unsolicited");
+
+ resolve()
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+ }, function* (targetBrowser) {
+ channel.send({ command: "unsolicited" }, {
+ browser: targetBrowser,
+ principal: Services.scriptSecurityManager.getSystemPrincipal()
+ });
+ yield messagePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel unsolicited send, using target origin's principal",
+ run: function* () {
+ let targetURI = Services.io.newURI(HTTP_PATH, null, null);
+ let channel = new WebChannel("echo", targetURI);
+
+ // an unsolicted message is sent from Chrome->Content which is then
+ // echoed back. If the echo is received here, then the content
+ // received the message.
+ let messagePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ is(id, "echo");
+ is(message.command, "unsolicited");
+
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+ }, function* (targetBrowser) {
+
+ channel.send({ command: "unsolicited" }, {
+ browser: targetBrowser,
+ principal: Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI)
+ });
+
+ yield messagePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel unsolicited send with principal mismatch",
+ run: function* () {
+ let targetURI = Services.io.newURI(HTTP_PATH, null, null);
+ let channel = new WebChannel("echo", targetURI);
+
+ // two unsolicited messages are sent from Chrome->Content. The first,
+ // `unsolicited_no_response_expected` is sent to the wrong principal
+ // and should not be echoed back. The second, `done`, is sent to the
+ // correct principal and should be echoed back.
+ let messagePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ is(id, "echo");
+
+ if (message.command === "done") {
+ resolve();
+ } else {
+ reject(new Error(`Unexpected command ${message.command}`));
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+ }, function* (targetBrowser) {
+
+ let mismatchURI = Services.io.newURI(HTTP_MISMATCH_PATH, null, null);
+ let mismatchPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(mismatchURI);
+
+ // send a message to the wrong principal. It should not be delivered
+ // to content, and should not be echoed back.
+ channel.send({ command: "unsolicited_no_response_expected" }, {
+ browser: targetBrowser,
+ principal: mismatchPrincipal
+ });
+
+ let targetPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI);
+
+ // send the `done` message to the correct principal. It
+ // should be echoed back.
+ channel.send({ command: "done" }, {
+ browser: targetBrowser,
+ principal: targetPrincipal
+ });
+
+ yield messagePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel non-window target",
+ run: function* () {
+ /**
+ * This test ensures messages can be received from and responses
+ * sent to non-window elements.
+ *
+ * First wait for the non-window element to send a "start" message.
+ * Then send the non-window element a "done" message.
+ * The non-window element will echo the "done" message back, if it
+ * receives the message.
+ * Listen for the response. If received, good to go!
+ */
+ let channel = new WebChannel("not_a_window", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ if (message.command === "start") {
+ channel.send({ command: "done" }, sender);
+ } else if (message.command === "done") {
+ resolve();
+ } else {
+ reject(new Error(`Unexpected command ${message.command}`));
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles"
+ }, function* () {
+ yield testDonePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel disallows non-string message from non-whitelisted origin",
+ run: function* () {
+ /**
+ * This test ensures that non-string messages can't be sent via WebChannels.
+ * We create a page (on a non-whitelisted origin) which should send us two
+ * messages immediately. The first message has an object for it's detail,
+ * and the second has a string. We check that we only get the second
+ * message.
+ */
+ let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null));
+ let testDonePromise = new Promise((resolve, reject) => {
+ channel.listen((id, message, sender) => {
+ is(id, "objects");
+ is(message.type, "string");
+ resolve();
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?object"
+ }, function* () {
+ yield testDonePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel allows both string and non-string message from whitelisted origin",
+ run: function* () {
+ /**
+ * Same process as above, but we whitelist the origin before loading the page,
+ * and expect to get *both* messages back (each exactly once).
+ */
+ let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise((resolve, reject) => {
+ let sawObject = false;
+ let sawString = false;
+ channel.listen((id, message, sender) => {
+ is(id, "objects");
+ if (message.type === "object") {
+ ok(!sawObject);
+ sawObject = true;
+ } else if (message.type === "string") {
+ ok(!sawString);
+ sawString = true;
+ } else {
+ reject(new Error(`Unknown message type: ${message.type}`))
+ }
+ if (sawObject && sawString) {
+ resolve();
+ }
+ });
+ });
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " " + HTTP_PATH;
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?object"
+ }, function* () {
+ yield testDonePromise;
+ Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist);
+ channel.stopListening();
+ });
+ }
+ }
+]; // gTests
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info("Running: " + testCase.desc);
+ yield testCase.run();
+ }
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_web_channel_iframe.html b/browser/base/content/test/general/browser_web_channel_iframe.html
new file mode 100644
index 000000000..7900e7530
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel_iframe.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>web_channel_test (iframe)</title>
+</head>
+<body>
+<script>
+ var REDIRECTED_IFRAME_SRC_ROOT = "http://example.org/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
+
+ window.onload = function() {
+ var testName = window.location.search.replace(/^\?/, "");
+ switch (testName) {
+ case "iframe":
+ test_iframe();
+ break;
+ case "iframe_pre_redirect":
+ test_iframe_pre_redirect();
+ break;
+ case "iframe_post_redirect":
+ test_iframe_post_redirect();
+ break;
+ default:
+ throw new Error(`INVALID TEST NAME ${testName}`);
+ }
+ };
+
+ function test_iframe() {
+ var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "one",
+ },
+ })
+ });
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "two",
+ detail: e.detail.message,
+ },
+ }),
+ });
+
+ if (!e.detail.message.error) {
+ window.dispatchEvent(secondMessage);
+ }
+ }, true);
+
+ window.dispatchEvent(firstMessage);
+ }
+
+
+ function test_iframe_pre_redirect() {
+ var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "pre_redirect",
+ message: {
+ command: "redirecting",
+ },
+ }),
+ });
+ window.dispatchEvent(firstMessage);
+ document.location = REDIRECTED_IFRAME_SRC_ROOT + "?iframe_post_redirect";
+ }
+
+ function test_iframe_post_redirect() {
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ var echoMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "post_redirect",
+ message: e.detail.message,
+ }),
+ });
+
+ window.dispatchEvent(echoMessage);
+ }, true);
+
+ // Let the test parent know the page has loaded and is ready to echo events
+ var loadedMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "post_redirect",
+ message: {
+ command: "loaded",
+ },
+ }),
+ });
+ window.dispatchEvent(loadedMessage);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_windowactivation.js b/browser/base/content/test/general/browser_windowactivation.js
new file mode 100644
index 000000000..ae4ba75dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_windowactivation.js
@@ -0,0 +1,183 @@
+/*
+ * This test checks that window activation state is set properly with multiple tabs.
+ */
+
+var testPage = "data:text/html,<body><style>:-moz-window-inactive { background-color: red; }</style><div id='area'></div></body>";
+
+var colorChangeNotifications = 0;
+var otherWindow;
+
+var browser1, browser2;
+
+function test() {
+ waitForExplicitFinish();
+ waitForFocus(reallyRunTests);
+}
+
+function reallyRunTests() {
+
+ let tab1 = gBrowser.addTab();
+ let tab2 = gBrowser.addTab();
+ browser1 = gBrowser.getBrowserForTab(tab1);
+ browser2 = gBrowser.getBrowserForTab(tab2);
+
+ gURLBar.focus();
+
+ var loadCount = 0;
+ function check()
+ {
+ // wait for both tabs to load
+ if (++loadCount != 2) {
+ return;
+ }
+
+ browser1.removeEventListener("load", check, true);
+ browser2.removeEventListener("load", check, true);
+
+ sendGetBackgroundRequest(true);
+ }
+
+ // The test performs four checks, using -moz-window-inactive on two child tabs.
+ // First, the initial state should be transparent. The second check is done
+ // while another window is focused. The third check is done after that window
+ // is closed and the main window focused again. The fourth check is done after
+ // switching to the second tab.
+ window.messageManager.addMessageListener("Test:BackgroundColorChanged", function(message) {
+ colorChangeNotifications++;
+
+ switch (colorChangeNotifications) {
+ case 1:
+ is(message.data.color, "transparent", "first window initial");
+ break;
+ case 2:
+ is(message.data.color, "transparent", "second window initial");
+ runOtherWindowTests();
+ break;
+ case 3:
+ is(message.data.color, "rgb(255, 0, 0)", "first window lowered");
+ break;
+ case 4:
+ is(message.data.color, "rgb(255, 0, 0)", "second window lowered");
+ sendGetBackgroundRequest(true);
+ otherWindow.close();
+ break;
+ case 5:
+ is(message.data.color, "transparent", "first window raised");
+ break;
+ case 6:
+ is(message.data.color, "transparent", "second window raised");
+ gBrowser.selectedTab = tab2;
+ break;
+ case 7:
+ is(message.data.color, "transparent", "first window after tab switch");
+ break;
+ case 8:
+ is(message.data.color, "transparent", "second window after tab switch");
+ finishTest();
+ break;
+ case 9:
+ ok(false, "too many color change notifications");
+ break;
+ }
+ });
+
+ window.messageManager.addMessageListener("Test:FocusReceived", function(message) {
+ // No color change should occur after a tab switch.
+ if (colorChangeNotifications == 6) {
+ sendGetBackgroundRequest(false);
+ }
+ });
+
+ window.messageManager.addMessageListener("Test:ActivateEvent", function(message) {
+ ok(message.data.ok, "Test:ActivateEvent");
+ });
+
+ window.messageManager.addMessageListener("Test:DeactivateEvent", function(message) {
+ ok(message.data.ok, "Test:DeactivateEvent");
+ });
+
+ browser1.addEventListener("load", check, true);
+ browser2.addEventListener("load", check, true);
+ browser1.contentWindow.location = testPage;
+ browser2.contentWindow.location = testPage;
+
+ browser1.messageManager.loadFrameScript("data:,(" + childFunction.toString() + ")();", true);
+ browser2.messageManager.loadFrameScript("data:,(" + childFunction.toString() + ")();", true);
+
+ gBrowser.selectedTab = tab1;
+}
+
+function sendGetBackgroundRequest(ifChanged)
+{
+ browser1.messageManager.sendAsyncMessage("Test:GetBackgroundColor", { ifChanged: ifChanged });
+ browser2.messageManager.sendAsyncMessage("Test:GetBackgroundColor", { ifChanged: ifChanged });
+}
+
+function runOtherWindowTests() {
+ otherWindow = window.open("data:text/html,<body>Hi</body>", "", "chrome");
+ waitForFocus(function () {
+ sendGetBackgroundRequest(true);
+ }, otherWindow);
+}
+
+function finishTest()
+{
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ otherWindow = null;
+ finish();
+}
+
+function childFunction()
+{
+ let oldColor = null;
+
+ let expectingResponse = false;
+ let ifChanged = true;
+
+ addMessageListener("Test:GetBackgroundColor", function(message) {
+ expectingResponse = true;
+ ifChanged = message.data.ifChanged;
+ });
+
+ content.addEventListener("focus", function () {
+ sendAsyncMessage("Test:FocusReceived", { });
+ }, false);
+
+ var windowGotActivate = false;
+ var windowGotDeactivate = false;
+ addEventListener("activate", function() {
+ sendAsyncMessage("Test:ActivateEvent", { ok: !windowGotActivate });
+ windowGotActivate = false;
+ });
+
+ addEventListener("deactivate", function() {
+ sendAsyncMessage("Test:DeactivateEvent", { ok: !windowGotDeactivate });
+ windowGotDeactivate = false;
+ });
+ content.addEventListener("activate", function() {
+ windowGotActivate = true;
+ });
+
+ content.addEventListener("deactivate", function() {
+ windowGotDeactivate = true;
+ });
+
+ content.setInterval(function () {
+ if (!expectingResponse) {
+ return;
+ }
+
+ let area = content.document.getElementById("area");
+ if (!area) {
+ return; /* hasn't loaded yet */
+ }
+
+ let color = content.getComputedStyle(area, "").backgroundColor;
+ if (oldColor != color || !ifChanged) {
+ expectingResponse = false;
+ oldColor = color;
+ sendAsyncMessage("Test:BackgroundColorChanged", { color: color });
+ }
+ }, 20);
+}
diff --git a/browser/base/content/test/general/browser_windowopen_reflows.js b/browser/base/content/test/general/browser_windowopen_reflows.js
new file mode 100644
index 000000000..7dac8aad6
--- /dev/null
+++ b/browser/base/content/test/general/browser_windowopen_reflows.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EXPECTED_REFLOWS = [
+ // handleEvent flushes layout to get the tabstrip width after a resize.
+ "handleEvent@chrome://browser/content/tabbrowser.xml|",
+
+ // Loading a tab causes a reflow.
+ "loadTabs@chrome://browser/content/tabbrowser.xml|" +
+ "loadOneOrMoreURIs@chrome://browser/content/browser.js|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
+
+ // Selecting the address bar causes a reflow.
+ "select@chrome://global/content/bindings/textbox.xml|" +
+ "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
+
+ // Focusing the content area causes a reflow.
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
+
+ // Sometimes sessionstore collects data during this test, which causes a sync reflow
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=892154 will fix this)
+ "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm",
+];
+
+if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
+ // TabsInTitlebar._update causes a reflow on OS X and Windows trying to do calculations
+ // since layout info is already dirty. This doesn't seem to happen before
+ // MozAfterPaint on Linux.
+ EXPECTED_REFLOWS.push("TabsInTitlebar._update/rect@chrome://browser/content/browser-tabsintitlebar.js|" +
+ "TabsInTitlebar._update@chrome://browser/content/browser-tabsintitlebar.js|" +
+ "updateAppearance@chrome://browser/content/browser-tabsintitlebar.js|" +
+ "handleEvent@chrome://browser/content/tabbrowser.xml|");
+}
+
+if (Services.appinfo.OS == "Darwin") {
+ // _onOverflow causes a reflow getting widths.
+ EXPECTED_REFLOWS.push("OverflowableToolbar.prototype._onOverflow@resource:///modules/CustomizableUI.jsm|" +
+ "OverflowableToolbar.prototype.init@resource:///modules/CustomizableUI.jsm|" +
+ "OverflowableToolbar.prototype.observe@resource:///modules/CustomizableUI.jsm|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|");
+ // Same as above since in packaged builds there are no function names and the resource URI includes "app"
+ EXPECTED_REFLOWS.push("@resource://app/modules/CustomizableUI.jsm|" +
+ "@resource://app/modules/CustomizableUI.jsm|" +
+ "@resource://app/modules/CustomizableUI.jsm|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|");
+}
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening new windows.
+ */
+function test() {
+ waitForExplicitFinish();
+
+ // Add a reflow observer and open a new window
+ let win = OpenBrowserWindow();
+ let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ docShell.addWeakReflowObserver(observer);
+
+ // Wait until the mozafterpaint event occurs.
+ waitForMozAfterPaint(win, function paintListener() {
+ // Remove reflow observer and clean up.
+ docShell.removeWeakReflowObserver(observer);
+ win.close();
+
+ finish();
+ });
+}
+
+var observer = {
+ reflow: function (start, end) {
+ // Gather information about the current code path.
+ let stack = new Error().stack;
+ let path = stack.split("\n").slice(1).map(line => {
+ return line.replace(/:\d+:\d+$/, "");
+ }).join("|");
+ let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
+
+ // Stack trace is empty. Reflow was triggered by native code.
+ if (path === "") {
+ return;
+ }
+
+ // Check if this is an expected reflow.
+ for (let expectedStack of EXPECTED_REFLOWS) {
+ if (path.startsWith(expectedStack) ||
+ // Accept an empty function name for gBrowserInit._delayedStartup or TabsInTitlebar._update to workaround bug 906578.
+ path.startsWith(expectedStack.replace(/(^|\|)(gBrowserInit\._delayedStartup|TabsInTitlebar\._update)@/, "$1@"))) {
+ ok(true, "expected uninterruptible reflow '" + expectedStack + "'");
+ return;
+ }
+ }
+
+ ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
+ },
+
+ reflowInterruptible: function (start, end) {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function waitForMozAfterPaint(win, callback) {
+ win.addEventListener("MozAfterPaint", function onEnd(event) {
+ if (event.target != win)
+ return;
+ win.removeEventListener("MozAfterPaint", onEnd);
+ executeSoon(callback);
+ });
+}
diff --git a/browser/base/content/test/general/browser_zbug569342.js b/browser/base/content/test/general/browser_zbug569342.js
new file mode 100644
index 000000000..2dac5acde
--- /dev/null
+++ b/browser/base/content/test/general/browser_zbug569342.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gTab = null;
+
+function load(url, cb) {
+ gTab = gBrowser.addTab(url);
+ gBrowser.addEventListener("load", function (event) {
+ if (event.target.location != url)
+ return;
+
+ gBrowser.removeEventListener("load", arguments.callee, true);
+ // Trigger onLocationChange by switching tabs.
+ gBrowser.selectedTab = gTab;
+ cb();
+ }, true);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(gFindBar.hidden, "Find bar should not be visible by default");
+
+ // Open the Find bar before we navigate to pages that shouldn't have it.
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(!gFindBar.hidden, "Find bar should be visible");
+
+ nextTest();
+}
+
+var urls = [
+ "about:config",
+ "about:addons",
+];
+
+function nextTest() {
+ let url = urls.shift();
+ if (url) {
+ testFindDisabled(url, nextTest);
+ } else {
+ // Make sure the find bar is re-enabled after disabled page is closed.
+ testFindEnabled("about:blank", function () {
+ EventUtils.synthesizeKey("VK_ESCAPE", { });
+ ok(gFindBar.hidden, "Find bar should now be hidden");
+ finish();
+ });
+ }
+}
+
+function testFindDisabled(url, cb) {
+ load(url, function() {
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ EventUtils.synthesizeKey("/", {}, gTab.linkedBrowser.contentWindow);
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ ok(document.getElementById("cmd_find").getAttribute("disabled"),
+ "Find command should be disabled");
+
+ gBrowser.removeTab(gTab);
+ cb();
+ });
+}
+
+function testFindEnabled(url, cb) {
+ load(url, function() {
+ ok(!document.getElementById("cmd_find").getAttribute("disabled"),
+ "Find command should not be disabled");
+
+ // Open Find bar and then close it.
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(!gFindBar.hidden, "Find bar should be visible again");
+ EventUtils.synthesizeKey("VK_ESCAPE", { });
+ ok(gFindBar.hidden, "Find bar should now be hidden");
+
+ gBrowser.removeTab(gTab);
+ cb();
+ });
+}
diff --git a/browser/base/content/test/general/bug1262648_string_with_newlines.dtd b/browser/base/content/test/general/bug1262648_string_with_newlines.dtd
new file mode 100644
index 000000000..308072c4e
--- /dev/null
+++ b/browser/base/content/test/general/bug1262648_string_with_newlines.dtd
@@ -0,0 +1,3 @@
+<!ENTITY foo.bar "This string
+contains
+newlines!"> \ No newline at end of file
diff --git a/browser/base/content/test/general/bug364677-data.xml b/browser/base/content/test/general/bug364677-data.xml
new file mode 100644
index 000000000..b48915c05
--- /dev/null
+++ b/browser/base/content/test/general/bug364677-data.xml
@@ -0,0 +1,5 @@
+<rss version="2.0">
+ <channel>
+ <title>t</title>
+ </channel>
+</rss>
diff --git a/browser/base/content/test/general/bug364677-data.xml^headers^ b/browser/base/content/test/general/bug364677-data.xml^headers^
new file mode 100644
index 000000000..f203c6368
--- /dev/null
+++ b/browser/base/content/test/general/bug364677-data.xml^headers^
@@ -0,0 +1 @@
+Content-Type: text/xml
diff --git a/browser/base/content/test/general/bug395533-data.txt b/browser/base/content/test/general/bug395533-data.txt
new file mode 100644
index 000000000..e0ed39850
--- /dev/null
+++ b/browser/base/content/test/general/bug395533-data.txt
@@ -0,0 +1,6 @@
+<rss version="2.0">
+ <channel>
+ <link>http://example.org/</link>
+ <title>t</title>
+ </channel>
+</rss>
diff --git a/browser/base/content/test/general/bug592338.html b/browser/base/content/test/general/bug592338.html
new file mode 100644
index 000000000..159b21a76
--- /dev/null
+++ b/browser/base/content/test/general/bug592338.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script type="text/javascript">
+var theme = {
+ id: "test",
+ name: "Test Background",
+ headerURL: "http://example.com/firefox/personas/01/header.jpg",
+ footerURL: "http://example.com/firefox/personas/01/footer.jpg",
+ textcolor: "#fff",
+ accentcolor: "#6b6b6b"
+};
+
+function setTheme(node) {
+ node.setAttribute("data-browsertheme", JSON.stringify(theme));
+ var event = document.createEvent("Events");
+ event.initEvent("InstallBrowserTheme", true, false);
+ node.dispatchEvent(event);
+}
+</script>
+</head>
+<body>
+<a id="theme-install" href="#" onclick="setTheme(this)">Install</a>
+</body>
+</html>
diff --git a/browser/base/content/test/general/bug792517-2.html b/browser/base/content/test/general/bug792517-2.html
new file mode 100644
index 000000000..bfc24d817
--- /dev/null
+++ b/browser/base/content/test/general/bug792517-2.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<a href="bug792517.sjs" id="fff">this is a link</a>
+</body>
+</html>
diff --git a/browser/base/content/test/general/bug792517.html b/browser/base/content/test/general/bug792517.html
new file mode 100644
index 000000000..e7c040bf1
--- /dev/null
+++ b/browser/base/content/test/general/bug792517.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<img src="moz.png" id="img">
+</body>
+</html>
diff --git a/browser/base/content/test/general/bug792517.sjs b/browser/base/content/test/general/bug792517.sjs
new file mode 100644
index 000000000..91e5aa23f
--- /dev/null
+++ b/browser/base/content/test/general/bug792517.sjs
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ if (aRequest.hasHeader('Cookie')) {
+ aResponse.write("cookie-present");
+ } else {
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+ aResponse.write("cookie-not-present");
+ }
+}
diff --git a/browser/base/content/test/general/bug839103.css b/browser/base/content/test/general/bug839103.css
new file mode 100644
index 000000000..611907d3d
--- /dev/null
+++ b/browser/base/content/test/general/bug839103.css
@@ -0,0 +1 @@
+* {}
diff --git a/browser/base/content/test/general/clipboard_pastefile.html b/browser/base/content/test/general/clipboard_pastefile.html
new file mode 100644
index 000000000..fcbf60ed2
--- /dev/null
+++ b/browser/base/content/test/general/clipboard_pastefile.html
@@ -0,0 +1,37 @@
+<html><body>
+<script>
+function checkPaste(event)
+{
+ let output = document.getElementById("output");
+ output.textContent = checkPasteHelper(event);
+}
+
+function checkPasteHelper(event)
+{
+ let dt = event.clipboardData;
+ if (dt.types.length != 2)
+ return "Wrong number of types; got " + dt.types.length;
+
+ for (let type of dt.types) {
+ if (type != "Files" && type != "application/x-moz-file")
+ return "Invalid type for types; got" + type;
+ }
+
+ for (let type of dt.mozTypesAt(0)) {
+ if (type != "Files" && type != "application/x-moz-file")
+ return "Invalid type for mozTypesAt; got" + type;
+ }
+
+ if (dt.getData("text/plain"))
+ return "text/plain found with getData";
+ if (dt.mozGetDataAt("text/plain", 0))
+ return "text/plain found with mozGetDataAt";
+
+ return "Passed";
+}
+</script>
+
+<input id="input" onpaste="checkPaste(event)">
+<div id="output"></div>
+
+</body></html>
diff --git a/browser/base/content/test/general/close_beforeunload.html b/browser/base/content/test/general/close_beforeunload.html
new file mode 100644
index 000000000..4b62002cc
--- /dev/null
+++ b/browser/base/content/test/general/close_beforeunload.html
@@ -0,0 +1,8 @@
+<body>
+ <p>I will close myself if you close me.</p>
+ <script>
+ window.onbeforeunload = function() {
+ window.close();
+ };
+ </script>
+</body>
diff --git a/browser/base/content/test/general/close_beforeunload_opens_second_tab.html b/browser/base/content/test/general/close_beforeunload_opens_second_tab.html
new file mode 100644
index 000000000..243307a0e
--- /dev/null
+++ b/browser/base/content/test/general/close_beforeunload_opens_second_tab.html
@@ -0,0 +1,3 @@
+<body>
+ <a href="#" onclick="window.open('close_beforeunload.html', '_blank')">Open second tab</a>
+</body>
diff --git a/browser/base/content/test/general/contentSearchUI.html b/browser/base/content/test/general/contentSearchUI.html
new file mode 100644
index 000000000..3750ac2b0
--- /dev/null
+++ b/browser/base/content/test/general/contentSearchUI.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+<head>
+<meta charset="utf-8">
+<script type="application/javascript;version=1.8"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js">
+</script>
+<script type="application/javascript;version=1.8"
+ src="chrome://browser/content/contentSearchUI.js">
+</script>
+<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/>
+</head>
+<body>
+
+<div id="container" style="position: relative;"><input type="text" value=""/></div>
+
+</body>
+</html>
diff --git a/browser/base/content/test/general/contentSearchUI.js b/browser/base/content/test/general/contentSearchUI.js
new file mode 100644
index 000000000..0e46230a2
--- /dev/null
+++ b/browser/base/content/test/general/contentSearchUI.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+(function () {
+
+const TEST_MSG = "ContentSearchUIControllerTest";
+const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
+var gController;
+
+addMessageListener(TEST_MSG, msg => {
+ messageHandlers[msg.data.type](msg.data.data);
+});
+
+var messageHandlers = {
+
+ init: function() {
+ Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME);
+ let input = content.document.querySelector("input");
+ gController =
+ new content.ContentSearchUIController(input, input.parentNode, "test", "test");
+ content.addEventListener("ContentSearchService", function listener(aEvent) {
+ if (aEvent.detail.type == "State" &&
+ gController.defaultEngine.name == ENGINE_NAME) {
+ content.removeEventListener("ContentSearchService", listener);
+ ack("init");
+ }
+ });
+ gController.remoteTimeout = 5000;
+ },
+
+ key: function (arg) {
+ let keyName = typeof(arg) == "string" ? arg : arg.key;
+ content.synthesizeKey(keyName, arg.modifiers || {});
+ let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
+ wait(ack.bind(null, "key"));
+ },
+
+ startComposition: function (arg) {
+ content.synthesizeComposition({ type: "compositionstart", data: "" });
+ ack("startComposition");
+ },
+
+ changeComposition: function (arg) {
+ let data = typeof(arg) == "string" ? arg : arg.data;
+ content.synthesizeCompositionChange({
+ composition: {
+ string: data,
+ clauses: [
+ { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: data.length, length: 0 }
+ });
+ let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
+ wait(ack.bind(null, "changeComposition"));
+ },
+
+ commitComposition: function () {
+ content.synthesizeComposition({ type: "compositioncommitasis" });
+ ack("commitComposition");
+ },
+
+ focus: function () {
+ gController.input.focus();
+ ack("focus");
+ },
+
+ blur: function () {
+ gController.input.blur();
+ ack("blur");
+ },
+
+ waitForSearch: function () {
+ waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
+ },
+
+ waitForSearchSettings: function () {
+ waitForContentSearchEvent("ManageEngines",
+ aData => ack("waitForSearchSettings", aData));
+ },
+
+ mousemove: function (itemIndex) {
+ let row;
+ if (itemIndex == -1) {
+ row = gController._table.firstChild;
+ }
+ else {
+ let allElts = [...gController._suggestionsList.children,
+ ...gController._oneOffButtons,
+ content.document.getElementById("contentSearchSettingsButton")];
+ row = allElts[itemIndex];
+ }
+ let event = {
+ type: "mousemove",
+ clickcount: 0,
+ }
+ row.addEventListener("mousemove", function handler() {
+ row.removeEventListener("mousemove", handler);
+ ack("mousemove");
+ });
+ content.synthesizeMouseAtCenter(row, event);
+ },
+
+ click: function (arg) {
+ let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg;
+ let row;
+ if (eltIdx == -1) {
+ row = gController._table.firstChild;
+ }
+ else {
+ let allElts = [...gController._suggestionsList.children,
+ ...gController._oneOffButtons,
+ content.document.getElementById("contentSearchSettingsButton")];
+ row = allElts[eltIdx];
+ }
+ let event = arg.modifiers || {};
+ // synthesizeMouseAtCenter defaults to sending a mousedown followed by a
+ // mouseup if the event type is not specified.
+ content.synthesizeMouseAtCenter(row, event);
+ ack("click");
+ },
+
+ addInputValueToFormHistory: function () {
+ gController.addInputValueToFormHistory();
+ ack("addInputValueToFormHistory");
+ },
+
+ addDuplicateOneOff: function () {
+ let btn = gController._oneOffButtons[gController._oneOffButtons.length - 1];
+ let newBtn = btn.cloneNode(true);
+ btn.parentNode.appendChild(newBtn);
+ gController._oneOffButtons.push(newBtn);
+ ack("addDuplicateOneOff");
+ },
+
+ removeLastOneOff: function () {
+ gController._oneOffButtons.pop().remove();
+ ack("removeLastOneOff");
+ },
+
+ reset: function () {
+ // Reset both the input and suggestions by select all + delete. If there was
+ // no text entered, this won't have any effect, so also escape to ensure the
+ // suggestions table is closed.
+ gController.input.focus();
+ content.synthesizeKey("a", { accelKey: true });
+ content.synthesizeKey("VK_DELETE", {});
+ content.synthesizeKey("VK_ESCAPE", {});
+ ack("reset");
+ },
+};
+
+function ack(aType, aData) {
+ sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
+}
+
+function waitForSuggestions(cb) {
+ let observer = new content.MutationObserver(() => {
+ if (gController.input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ cb();
+ }
+ });
+ observer.observe(gController.input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+}
+
+function waitForContentSearchEvent(messageType, cb) {
+ let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(content.SpecialPowers.Ci.nsIMessageListenerManager);
+ mm.addMessageListener("ContentSearch", function listener(aMsg) {
+ if (aMsg.data.type != messageType) {
+ return;
+ }
+ mm.removeMessageListener("ContentSearch", listener);
+ cb(aMsg.data.data);
+ });
+}
+
+function currentState() {
+ let state = {
+ selectedIndex: gController.selectedIndex,
+ selectedButtonIndex: gController.selectedButtonIndex,
+ numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
+ suggestionAtIndex: [],
+ isFormHistorySuggestionAtIndex: [],
+
+ tableHidden: gController._table.hidden,
+
+ inputValue: gController.input.value,
+ ariaExpanded: gController.input.getAttribute("aria-expanded"),
+ };
+
+ if (state.numSuggestions) {
+ for (let i = 0; i < gController.numSuggestions; i++) {
+ state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
+ state.isFormHistorySuggestionAtIndex.push(
+ gController.isFormHistorySuggestionAtIndex(i));
+ }
+ }
+
+ return state;
+}
+
+})();
diff --git a/browser/base/content/test/general/content_aboutAccounts.js b/browser/base/content/test/general/content_aboutAccounts.js
new file mode 100644
index 000000000..12ac04934
--- /dev/null
+++ b/browser/base/content/test/general/content_aboutAccounts.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is loaded as a "content script" for browser_aboutAccounts tests
+"use strict";
+
+var {interfaces: Ci, utils: Cu} = Components;
+
+addEventListener("load", function load(event) {
+ if (event.target != content.document) {
+ return;
+ }
+// content.document.removeEventListener("load", load, true);
+ sendAsyncMessage("test:document:load");
+ // Opening Sync prefs in tests is a pain as leaks are reported due to the
+ // in-flight promises. For now we just mock the openPrefs() function and have
+ // it send a message back to the test so we know it was called.
+ content.openPrefs = function() {
+ sendAsyncMessage("test:openPrefsCalled");
+ }
+}, true);
+
+addEventListener("DOMContentLoaded", function domContentLoaded(event) {
+ removeEventListener("DOMContentLoaded", domContentLoaded, true);
+ let iframe = content.document.getElementById("remote");
+ if (!iframe) {
+ // at least one test initially loads about:blank - in that case, we are done.
+ return;
+ }
+ // We use DOMContentLoaded here as that fires for our iframe even when we've
+ // arranged for the URL in the iframe to cause an error.
+ addEventListener("DOMContentLoaded", function iframeLoaded(dclEvent) {
+ if (iframe.contentWindow.location.href == "about:blank" ||
+ dclEvent.target != iframe.contentDocument) {
+ return;
+ }
+ removeEventListener("DOMContentLoaded", iframeLoaded, true);
+ sendAsyncMessage("test:iframe:load", {url: iframe.contentDocument.location.href});
+ // And an event listener for the test responses, which we send to the test
+ // via a message.
+ iframe.contentWindow.addEventListener("FirefoxAccountsTestResponse", function (fxAccountsEvent) {
+ sendAsyncMessage("test:response", {data: fxAccountsEvent.detail.data});
+ }, true);
+ }, true);
+}, true);
+
+// Return the visibility state of a list of ids.
+addMessageListener("test:check-visibilities", function (message) {
+ let result = {};
+ for (let id of message.data.ids) {
+ let elt = content.document.getElementById(id);
+ if (elt) {
+ let displayStyle = content.window.getComputedStyle(elt).display;
+ if (displayStyle == 'none') {
+ result[id] = false;
+ } else if (displayStyle == 'block') {
+ result[id] = true;
+ } else {
+ result[id] = "strange: " + displayStyle; // tests should fail!
+ }
+ } else {
+ result[id] = "doesn't exist: " + id;
+ }
+ }
+ sendAsyncMessage("test:check-visibilities-response", result);
+});
+
+addMessageListener("test:load-with-mocked-profile-path", function (message) {
+ addEventListener("DOMContentLoaded", function domContentLoaded(event) {
+ removeEventListener("DOMContentLoaded", domContentLoaded, true);
+ content.getDefaultProfilePath = () => message.data.profilePath;
+ // now wait for the iframe to load.
+ let iframe = content.document.getElementById("remote");
+ iframe.addEventListener("load", function iframeLoaded(loadEvent) {
+ if (iframe.contentWindow.location.href == "about:blank" ||
+ loadEvent.target != iframe) {
+ return;
+ }
+ iframe.removeEventListener("load", iframeLoaded, true);
+ sendAsyncMessage("test:load-with-mocked-profile-path-response",
+ {url: iframe.contentDocument.location.href});
+ }, true);
+ });
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(message.data.url, webNav.LOAD_FLAGS_NONE, null, null, null);
+}, true);
diff --git a/browser/base/content/test/general/contextmenu_common.js b/browser/base/content/test/general/contextmenu_common.js
new file mode 100644
index 000000000..1a0fa931a
--- /dev/null
+++ b/browser/base/content/test/general/contextmenu_common.js
@@ -0,0 +1,324 @@
+var lastElement;
+
+function openContextMenuFor(element, shiftkey, waitForSpellCheck) {
+ // Context menu should be closed before we open it again.
+ is(SpecialPowers.wrap(contextMenu).state, "closed", "checking if popup is closed");
+
+ if (lastElement)
+ lastElement.blur();
+ element.focus();
+
+ // Some elements need time to focus and spellcheck before any tests are
+ // run on them.
+ function actuallyOpenContextMenuFor() {
+ lastElement = element;
+ var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
+ synthesizeMouse(element, 2, 2, eventDetails, element.ownerGlobal);
+ }
+
+ if (waitForSpellCheck) {
+ var { onSpellCheck } = SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {});
+ onSpellCheck(element, actuallyOpenContextMenuFor);
+ }
+ else {
+ actuallyOpenContextMenuFor();
+ }
+}
+
+function closeContextMenu() {
+ contextMenu.hidePopup();
+}
+
+function getVisibleMenuItems(aMenu, aData) {
+ var items = [];
+ var accessKeys = {};
+ for (var i = 0; i < aMenu.childNodes.length; i++) {
+ var item = aMenu.childNodes[i];
+ if (item.hidden)
+ continue;
+
+ var key = item.accessKey;
+ if (key)
+ key = key.toLowerCase();
+
+ var isPageMenuItem = item.hasAttribute("generateditemid");
+
+ if (item.nodeName == "menuitem") {
+ var isGenerated = item.className == "spell-suggestion"
+ || item.className == "sendtab-target";
+ if (isGenerated) {
+ is(item.id, "", "child menuitem #" + i + " is generated");
+ } else if (isPageMenuItem) {
+ is(item.id, "", "child menuitem #" + i + " is a generated page menu item");
+ } else {
+ ok(item.id, "child menuitem #" + i + " has an ID");
+ }
+ var label = item.getAttribute("label");
+ ok(label.length, "menuitem " + item.id + " has a label");
+ if (isGenerated) {
+ is(key, "", "Generated items shouldn't have an access key");
+ items.push("*" + label);
+ } else if (isPageMenuItem) {
+ items.push("+" + label);
+ } else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
+ item.id != "spell-no-suggestions" &&
+ item.id != "spell-add-dictionaries-main" &&
+ item.id != "context-savelinktopocket" &&
+ item.id != "fill-login-saved-passwords" &&
+ item.id != "fill-login-no-logins") {
+ ok(key, "menuitem " + item.id + " has an access key");
+ if (accessKeys[key])
+ ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ if (!isGenerated && !isPageMenuItem) {
+ items.push(item.id);
+ }
+ if (isPageMenuItem) {
+ var p = {};
+ p.type = item.getAttribute("type");
+ p.icon = item.getAttribute("image");
+ p.checked = item.hasAttribute("checked");
+ p.disabled = item.hasAttribute("disabled");
+ items.push(p);
+ } else {
+ items.push(!item.disabled);
+ }
+ } else if (item.nodeName == "menuseparator") {
+ ok(true, "--- seperator id is " + item.id);
+ items.push("---");
+ items.push(null);
+ } else if (item.nodeName == "menu") {
+ if (isPageMenuItem) {
+ item.id = "generated-submenu-" + aData.generatedSubmenuId++;
+ }
+ ok(item.id, "child menu #" + i + " has an ID");
+ if (!isPageMenuItem) {
+ ok(key, "menu has an access key");
+ if (accessKeys[key])
+ ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ items.push(item.id);
+ items.push(!item.disabled);
+ // Add a dummy item so that the indexes in checkMenu are the same
+ // for expectedItems and actualItems.
+ items.push([]);
+ items.push(null);
+ } else if (item.nodeName == "menugroup") {
+ ok(item.id, "child menugroup #" + i + " has an ID");
+ items.push(item.id);
+ items.push(!item.disabled);
+ var menugroupChildren = [];
+ for (var child of item.children) {
+ if (child.hidden)
+ continue;
+
+ menugroupChildren.push([child.id, !child.disabled]);
+ }
+ items.push(menugroupChildren);
+ items.push(null);
+ } else {
+ ok(false, "child #" + i + " of menu ID " + aMenu.id +
+ " has an unknown type (" + item.nodeName + ")");
+ }
+ }
+ return items;
+}
+
+function checkContextMenu(expectedItems) {
+ is(contextMenu.state, "open", "checking if popup is open");
+ var data = { generatedSubmenuId: 1 };
+ checkMenu(contextMenu, expectedItems, data);
+}
+
+function checkMenuItem(actualItem, actualEnabled, expectedItem, expectedEnabled, index) {
+ is(actualItem, expectedItem,
+ "checking item #" + index/2 + " (" + expectedItem + ") name");
+
+ if (typeof expectedEnabled == "object" && expectedEnabled != null ||
+ typeof actualEnabled == "object" && actualEnabled != null) {
+
+ ok(!(actualEnabled == null), "actualEnabled is not null");
+ ok(!(expectedEnabled == null), "expectedEnabled is not null");
+ is(typeof actualEnabled, typeof expectedEnabled, "checking types");
+
+ if (typeof actualEnabled != typeof expectedEnabled ||
+ actualEnabled == null || expectedEnabled == null)
+ return;
+
+ is(actualEnabled.type, expectedEnabled.type,
+ "checking item #" + index/2 + " (" + expectedItem + ") type attr value");
+ var icon = actualEnabled.icon;
+ if (icon) {
+ var tmp = "";
+ var j = icon.length - 1;
+ while (j && icon[j] != "/") {
+ tmp = icon[j--] + tmp;
+ }
+ icon = tmp;
+ }
+ is(icon, expectedEnabled.icon,
+ "checking item #" + index/2 + " (" + expectedItem + ") icon attr value");
+ is(actualEnabled.checked, expectedEnabled.checked,
+ "checking item #" + index/2 + " (" + expectedItem + ") has checked attr");
+ is(actualEnabled.disabled, expectedEnabled.disabled,
+ "checking item #" + index/2 + " (" + expectedItem + ") has disabled attr");
+ } else if (expectedEnabled != null)
+ is(actualEnabled, expectedEnabled,
+ "checking item #" + index/2 + " (" + expectedItem + ") enabled state");
+}
+
+/*
+ * checkMenu - checks to see if the specified <menupopup> contains the
+ * expected items and state.
+ * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
+ * the item is enabled or not (or null to ignore it). Submenus can be checked
+ * by providing a nested array entry after the expected <menu> ID.
+ * For example: ["blah", true, // item enabled
+ * "submenu", null, // submenu
+ * ["sub1", true, // submenu contents
+ * "sub2", false], null, // submenu contents
+ * "lol", false] // item disabled
+ *
+ */
+function checkMenu(menu, expectedItems, data) {
+ var actualItems = getVisibleMenuItems(menu, data);
+ // ok(false, "Items are: " + actualItems);
+ for (var i = 0; i < expectedItems.length; i+=2) {
+ var actualItem = actualItems[i];
+ var actualEnabled = actualItems[i + 1];
+ var expectedItem = expectedItems[i];
+ var expectedEnabled = expectedItems[i + 1];
+ if (expectedItem instanceof Array) {
+ ok(true, "Checking submenu/menugroup...");
+ var previousId = expectedItems[i - 2]; // The last item was the menu ID.
+ var previousItem = menu.getElementsByAttribute("id", previousId)[0];
+ ok(previousItem, (previousItem ? previousItem.nodeName : "item") + " with previous id (" + previousId + ") found");
+ if (previousItem && previousItem.nodeName == "menu") {
+ ok(previousItem, "got a submenu element of id='" + previousId + "'");
+ is(previousItem.nodeName, "menu", "submenu element of id='" + previousId +
+ "' has expected nodeName");
+ checkMenu(previousItem.menupopup, expectedItem, data, i);
+ } else if (previousItem && previousItem.nodeName == "menugroup") {
+ ok(expectedItem.length, "menugroup must not be empty");
+ for (var j = 0; j < expectedItem.length / 2; j++) {
+ checkMenuItem(actualItems[i][j][0], actualItems[i][j][1], expectedItem[j*2], expectedItem[j*2+1], i+j*2);
+ }
+ i += j;
+ } else {
+ ok(false, "previous item is not a menu or menugroup");
+ }
+ } else {
+ checkMenuItem(actualItem, actualEnabled, expectedItem, expectedEnabled, i);
+ }
+ }
+ // Could find unexpected extra items at the end...
+ is(actualItems.length, expectedItems.length, "checking expected number of menu entries");
+}
+
+let lastElementSelector = null;
+/**
+ * Right-clicks on the element that matches `selector` and checks the
+ * context menu that appears against the `menuItems` array.
+ *
+ * @param {String} selector
+ * A selector passed to querySelector to find
+ * the element that will be referenced.
+ * @param {Array} menuItems
+ * An array of menuitem ids and their associated enabled state. A state
+ * of null means that it will be ignored. Ids of '---' are used for
+ * menuseparators.
+ * @param {Object} options, optional
+ * skipFocusChange: don't move focus to the element before test, useful
+ * if you want to delay spell-check initialization
+ * offsetX: horizontal mouse offset from the top-left corner of
+ * the element, optional
+ * offsetY: vertical mouse offset from the top-left corner of the
+ * element, optional
+ * centered: if true, mouse position is centered in element, defaults
+ * to true if offsetX and offsetY are not provided
+ * waitForSpellCheck: wait until spellcheck is initialized before
+ * starting test
+ * preCheckContextMenuFn: callback to run before opening menu
+ * onContextMenuShown: callback to run when the context menu is shown
+ * postCheckContextMenuFn: callback to run after opening menu
+ * @return {Promise} resolved after the test finishes
+ */
+function* test_contextmenu(selector, menuItems, options={}) {
+ contextMenu = document.getElementById("contentAreaContextMenu");
+ is(contextMenu.state, "closed", "checking if popup is closed");
+
+ // Default to centered if no positioning is defined.
+ if (!options.offsetX && !options.offsetY) {
+ options.centered = true;
+ }
+
+ if (!options.skipFocusChange) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser,
+ [lastElementSelector, selector],
+ function*([contentLastElementSelector, contentSelector]) {
+ if (contentLastElementSelector) {
+ let contentLastElement = content.document.querySelector(contentLastElementSelector);
+ contentLastElement.blur();
+ }
+ let element = content.document.querySelector(contentSelector);
+ element.focus();
+ });
+ lastElementSelector = selector;
+ info(`Moved focus to ${selector}`);
+ }
+
+ if (options.preCheckContextMenuFn) {
+ yield options.preCheckContextMenuFn();
+ info("Completed preCheckContextMenuFn");
+ }
+
+ if (options.waitForSpellCheck) {
+ info("Waiting for spell check");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, selector, function*(contentSelector) {
+ let {onSpellCheck} = Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {});
+ let element = content.document.querySelector(contentSelector);
+ yield new Promise(resolve => onSpellCheck(element, resolve));
+ info("Spell check running");
+ });
+ }
+
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse(selector, options.offsetX || 0, options.offsetY || 0, {
+ type: "contextmenu",
+ button: 2,
+ shiftkey: options.shiftkey,
+ centered: options.centered
+ },
+ gBrowser.selectedBrowser);
+ yield awaitPopupShown;
+ info("Popup Shown");
+
+ if (options.onContextMenuShown) {
+ yield options.onContextMenuShown();
+ info("Completed onContextMenuShown");
+ }
+
+ if (menuItems) {
+ if (Services.prefs.getBoolPref("devtools.inspector.enabled")) {
+ let inspectItems = ["---", null,
+ "context-inspect", true];
+ menuItems = menuItems.concat(inspectItems);
+ }
+
+ checkContextMenu(menuItems);
+ }
+
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+
+ if (options.postCheckContextMenuFn) {
+ yield options.postCheckContextMenuFn();
+ info("Completed postCheckContextMenuFn");
+ }
+
+ contextMenu.hidePopup();
+ yield awaitPopupHidden;
+}
diff --git a/browser/base/content/test/general/ctxmenu-image.png b/browser/base/content/test/general/ctxmenu-image.png
new file mode 100644
index 000000000..4c3be5084
--- /dev/null
+++ b/browser/base/content/test/general/ctxmenu-image.png
Binary files differ
diff --git a/browser/base/content/test/general/discovery.html b/browser/base/content/test/general/discovery.html
new file mode 100644
index 000000000..1679e6545
--- /dev/null
+++ b/browser/base/content/test/general/discovery.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+ <head id="linkparent">
+ <title>Autodiscovery Test</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/download_page.html b/browser/base/content/test/general/download_page.html
new file mode 100644
index 000000000..4f9154033
--- /dev/null
+++ b/browser/base/content/test/general/download_page.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=676619
+-->
+ <head>
+ <title>Test for the download attribute</title>
+
+ </head>
+ <body>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=676619">Bug 676619</a>
+ <br/>
+ <ul>
+ <li><a href="data:text/plain,Hey What are you looking for?"
+ download="test.txt" id="link1">Download "test.txt"</a></li>
+ <li><a href="video.ogg"
+ download id="link2">Download "video.ogg"</a></li>
+ <li><a href="video.ogg"
+ download="just some video" id="link3">Download "just some video"</a></li>
+ <li><a href="data:text/plain,test"
+ download="with-target.txt" id="link4">Download "with-target.txt"</a></li>
+ <li><a href="javascript:(1+2)+''"
+ download="javascript.txt" id="link5">Download "javascript.txt"</a></li>
+ </ul>
+ <script>
+ var li = document.createElement('li');
+ var a = document.createElement('a');
+
+ a.href = window.URL.createObjectURL(new Blob(["just text"])) ;
+ a.download = "test.blob";
+ a.id = "link6";
+ a.textContent = 'Download "test.blob"';
+
+ li.appendChild(a);
+ document.getElementsByTagName('ul')[0].appendChild(li);
+
+ window.addEventListener("beforeunload", function (evt) {
+ document.getElementById("unload-flag").textContent = "Fail";
+ });
+ </script>
+ <ul>
+ <li><a href="http://example.com/"
+ download="example.com" id="link7" target="_blank">Download "example.com"</a></li>
+ <ul>
+ <div id="unload-flag">Okay</div>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/dummy_page.html b/browser/base/content/test/general/dummy_page.html
new file mode 100644
index 000000000..1a87e2840
--- /dev/null
+++ b/browser/base/content/test/general/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/feed_discovery.html b/browser/base/content/test/general/feed_discovery.html
new file mode 100644
index 000000000..baecba19b
--- /dev/null
+++ b/browser/base/content/test/general/feed_discovery.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377611
+-->
+ <head>
+ <title>Test for feed discovery</title>
+ <meta charset="utf-8">
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ <!-- rel is a space-separated list -->
+ <link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
+ <link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" />
+ <link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" />
+ <link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" />
+ <link rel="meat feed cake" title="8" href="/8.atom" />
+
+ <!-- rel is case-insensitive -->
+ <link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" />
+ <link rel="fEEd" title="10" href="/10.atom" />
+
+ <!-- type can have leading and trailing whitespace -->
+ <link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" />
+
+ <!-- type is case-insensitive -->
+ <link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" />
+
+ <!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't -->
+ <link rel="feed stylesheet" title="13" href="/13.atom" />
+
+ <!-- hyphens or letters around rel not allowed -->
+ <link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" />
+ <link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" />
+ <link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" />
+
+ <!-- don't tolerate text/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" />
+
+ <!-- don't tolerate application/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" />
+
+ <!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" />
+
+ <!-- don't tolerate random types -->
+ <link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" />
+
+ <!-- don't find Atom by title -->
+ <link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" />
+
+ <!-- don't find application/rss+xml by title -->
+ <link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" />
+
+ <!-- don't find application/rdf+xml by title -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" />
+
+ <!-- don't find application/xml by title -->
+ <link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" />
+
+ <!-- don't find text/xml by title -->
+ <link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
+
+ <!-- alternate and stylesheet isn't a feed -->
+ <link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
+ </head>
+ <body>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/general/feed_tab.html b/browser/base/content/test/general/feed_tab.html
new file mode 100644
index 000000000..50903f48b
--- /dev/null
+++ b/browser/base/content/test/general/feed_tab.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458579
+-->
+ <head>
+ <title>Test for page info feeds tab</title>
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_bug1045809_1.html b/browser/base/content/test/general/file_bug1045809_1.html
new file mode 100644
index 000000000..9baf2d45d
--- /dev/null
+++ b/browser/base/content/test/general/file_bug1045809_1.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <iframe src="http://test1.example.com/browser/browser/base/content/test/general/file_bug1045809_2.html"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_bug1045809_2.html b/browser/base/content/test/general/file_bug1045809_2.html
new file mode 100644
index 000000000..67a297dbc
--- /dev/null
+++ b/browser/base/content/test/general/file_bug1045809_2.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <div id="mixedContentContainer">Mixed Content is here</div>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_1.html b/browser/base/content/test/general/file_bug822367_1.html
new file mode 100644
index 000000000..62f42d226
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 1 for Mixed Content Blocker User Override - Mixed Script
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_1.js b/browser/base/content/test/general/file_bug822367_1.js
new file mode 100644
index 000000000..175de363b
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_1.js
@@ -0,0 +1 @@
+document.getElementById('p1').innerHTML="hello";
diff --git a/browser/base/content/test/general/file_bug822367_2.html b/browser/base/content/test/general/file_bug822367_2.html
new file mode 100644
index 000000000..fe56ee213
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 2 for Mixed Content Blocker User Override - Mixed Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 822367 - Mixed Display</title>
+</head>
+<body>
+ <div id="testContent">
+ <img src="http://example.com/tests/image/test/mochitest/blue.png">
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_3.html b/browser/base/content/test/general/file_bug822367_3.html
new file mode 100644
index 000000000..c1ff2c000
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_3.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 3 for Mixed Content Blocker User Override - Mixed Script and Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 822367</title>
+ <script>
+ function foo() {
+ var x = document.createElement('p');
+ x.setAttribute("id", "p2");
+ x.innerHTML = "bye";
+ document.getElementById("testContent").appendChild(x);
+ }
+ </script>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ <img src="http://example.com/tests/image/test/mochitest/blue.png" onload="foo()">
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_4.html b/browser/base/content/test/general/file_bug822367_4.html
new file mode 100644
index 000000000..9a073143f
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_4.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 4 for Mixed Content Blocker User Override - Mixed Script and Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 4 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_4.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_4.js b/browser/base/content/test/general/file_bug822367_4.js
new file mode 100644
index 000000000..301db89c7
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_4.js
@@ -0,0 +1 @@
+document.location = "https://example.com/browser/browser/base/content/test/general/file_bug822367_4B.html";
diff --git a/browser/base/content/test/general/file_bug822367_4B.html b/browser/base/content/test/general/file_bug822367_4B.html
new file mode 100644
index 000000000..76ea2b623
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_4B.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 4B for Mixed Content Blocker User Override - Location Changed
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 4B Location Change for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_5.html b/browser/base/content/test/general/file_bug822367_5.html
new file mode 100644
index 000000000..3c9a9317e
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_5.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 5 for Mixed Content Blocker User Override - Mixed Script in document.open()
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 5 for Bug 822367</title>
+ <script>
+ function createDoc()
+ {
+ var doc=document.open("text/html", "replace");
+ doc.write('<!DOCTYPE html><html><body><p id="p1">This is some content</p><script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">\<\/script\>\<\/body>\<\/html>');
+ doc.close();
+ }
+ </script>
+</head>
+<body>
+ <div id="testContent">
+ <img src="https://example.com/tests/image/test/mochitest/blue.png" onload="createDoc()">
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_6.html b/browser/base/content/test/general/file_bug822367_6.html
new file mode 100644
index 000000000..baa5674c2
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_6.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 6 for Mixed Content Blocker User Override - Mixed Script in document.open() within an iframe
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 6 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <iframe name="f1" id="f1" src="https://example.com/browser/browser/base/content/test/general/file_bug822367_5.html"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug902156.js b/browser/base/content/test/general/file_bug902156.js
new file mode 100644
index 000000000..f943dd628
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled";
diff --git a/browser/base/content/test/general/file_bug902156_1.html b/browser/base/content/test/general/file_bug902156_1.html
new file mode 100644
index 000000000..e3625de99
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156_1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug902156_2.html b/browser/base/content/test/general/file_bug902156_2.html
new file mode 100644
index 000000000..25aff3349
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156_2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <a href="https://test2.example.com/browser/browser/base/content/test/general/file_bug902156_1.html"
+ id="mctestlink" target="_top">Go to http site</a>
+ <script src="http://test2.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug902156_3.html b/browser/base/content/test/general/file_bug902156_3.html
new file mode 100644
index 000000000..65805adff
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156_3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190.js b/browser/base/content/test/general/file_bug906190.js
new file mode 100644
index 000000000..f943dd628
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled";
diff --git a/browser/base/content/test/general/file_bug906190.sjs b/browser/base/content/test/general/file_bug906190.sjs
new file mode 100644
index 000000000..bff126874
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ var page = "<!DOCTYPE html><html><body>bug 906190</body></html>";
+ var path = "https://test1.example.com/browser/browser/base/content/test/general/";
+ var url;
+
+ if (request.queryString.includes('bad-redirection=1')) {
+ url = path + "this_page_does_not_exist.html";
+ } else {
+ url = path + "file_bug906190_redirected.html";
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "302", "Found");
+ response.setHeader("Location", url, false);
+ response.write(page);
+}
diff --git a/browser/base/content/test/general/file_bug906190_1.html b/browser/base/content/test/general/file_bug906190_1.html
new file mode 100644
index 000000000..cbb3cac26
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 906190</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190_2.html b/browser/base/content/test/general/file_bug906190_2.html
new file mode 100644
index 000000000..70c7c61cf
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 906190</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test2.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190_3_4.html b/browser/base/content/test/general/file_bug906190_3_4.html
new file mode 100644
index 000000000..aea6648a9
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_3_4.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 and 4 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="refresh" content="0; url=https://test1.example.com/browser/browser/base/content/test/general/file_bug906190_redirected.html">
+ <title>Test 3 and 4 for Bug 906190</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190_redirected.html b/browser/base/content/test/general/file_bug906190_redirected.html
new file mode 100644
index 000000000..cc324bd25
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_redirected.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Redirected Page of Test 3 to 6 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Redirected Page for Bug 906190</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug970276_favicon1.ico b/browser/base/content/test/general/file_bug970276_favicon1.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_favicon1.ico
Binary files differ
diff --git a/browser/base/content/test/general/file_bug970276_favicon2.ico b/browser/base/content/test/general/file_bug970276_favicon2.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_favicon2.ico
Binary files differ
diff --git a/browser/base/content/test/general/file_bug970276_popup1.html b/browser/base/content/test/general/file_bug970276_popup1.html
new file mode 100644
index 000000000..5ce7dab87
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_popup1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bug 970276.</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_bug970276_favicon1.ico">
+</head>
+<body>
+ Test file for bug 970276.
+
+ <iframe src="file_bug970276_popup2.html">
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug970276_popup2.html b/browser/base/content/test/general/file_bug970276_popup2.html
new file mode 100644
index 000000000..0b9e5294e
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_popup2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bug 970276.</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_bug970276_favicon2.ico">
+</head>
+<body>
+ Test inner file for bug 970276.
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_csp_block_all_mixedcontent.html b/browser/base/content/test/general/file_csp_block_all_mixedcontent.html
new file mode 100644
index 000000000..93a7f13d9
--- /dev/null
+++ b/browser/base/content/test/general/file_csp_block_all_mixedcontent.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html><head><meta charset="utf-8">
+<title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+</head>
+<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
+<body>
+<script src="http://example.com/browser/browser/base/content/test/general/file_csp_block_all_mixedcontent.js"/>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_csp_block_all_mixedcontent.js b/browser/base/content/test/general/file_csp_block_all_mixedcontent.js
new file mode 100644
index 000000000..dc6d6a64e
--- /dev/null
+++ b/browser/base/content/test/general/file_csp_block_all_mixedcontent.js
@@ -0,0 +1,3 @@
+// empty script file just used for testing Bug 1122236.
+// Making sure the UI is not degraded when blocking
+// mixed content using the CSP directive: block-all-mixed-content.
diff --git a/browser/base/content/test/general/file_documentnavigation_frameset.html b/browser/base/content/test/general/file_documentnavigation_frameset.html
new file mode 100644
index 000000000..beb01addf
--- /dev/null
+++ b/browser/base/content/test/general/file_documentnavigation_frameset.html
@@ -0,0 +1,12 @@
+<html id="outer">
+
+<frameset rows="30%, 70%">
+ <frame src="data:text/html,&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 000000000..0bead5efc
--- /dev/null
+++ b/browser/base/content/test/general/file_double_close_tab.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test page that blocks beforeunload. Used in tests for bug 1050638 and bug 305085</title>
+ </head>
+ <body>
+ This page will block beforeunload. It should still be user-closable at all times.
+ <script>
+ window.onbeforeunload = function() {
+ return "stop";
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_favicon_change.html b/browser/base/content/test/general/file_favicon_change.html
new file mode 100644
index 000000000..18ac6526b
--- /dev/null
+++ b/browser/base/content/test/general/file_favicon_change.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i">
+</head>
+<body>
+ <script>
+ window.addEventListener("PleaseChangeFavicon", function() {
+ var ico = document.getElementById("i");
+ ico.setAttribute("href", "http://example.org/other-icon");
+ });
+ </script>
+</body></html>
diff --git a/browser/base/content/test/general/file_favicon_change_not_in_document.html b/browser/base/content/test/general/file_favicon_change_not_in_document.html
new file mode 100644
index 000000000..deebb07dc
--- /dev/null
+++ b/browser/base/content/test/general/file_favicon_change_not_in_document.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i">
+</head>
+<body onload="onload()">
+ <script>
+ function onload() {
+ var ico = document.createElement("link");
+ ico.setAttribute("rel", "icon");
+ ico.setAttribute("type", "image/ico");
+ ico.setAttribute("href", "http://example.org/other-icon");
+ setTimeout(function() {
+ ico.setAttribute("href", "http://example.org/yet-another-icon");
+ document.getElementById("i").remove();
+ document.head.appendChild(ico);
+ }, 1000);
+ }
+ </script>
+</body></html>
+
diff --git a/browser/base/content/test/general/file_fullscreen-window-open.html b/browser/base/content/test/general/file_fullscreen-window-open.html
new file mode 100644
index 000000000..1584f4c98
--- /dev/null
+++ b/browser/base/content/test/general/file_fullscreen-window-open.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for window.open() when browser is in fullscreen</title>
+ </head>
+ <body>
+ <script>
+ window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, true);
+
+ document.getElementById("test").addEventListener("click", onClick, true);
+ }, true);
+
+ function onClick(aEvent) {
+ aEvent.preventDefault();
+
+ var dataStr = aEvent.target.getAttribute("data-test-param");
+ var data = JSON.parse(dataStr);
+ window.open(data.uri, data.title, data.option);
+ }
+ </script>
+ <a id="test" href="" data-test-param="">Test</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_generic_favicon.ico b/browser/base/content/test/general/file_generic_favicon.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/general/file_generic_favicon.ico
Binary files differ
diff --git a/browser/base/content/test/general/file_mediaPlayback.html b/browser/base/content/test/general/file_mediaPlayback.html
new file mode 100644
index 000000000..a6979287e
--- /dev/null
+++ b/browser/base/content/test/general/file_mediaPlayback.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<audio src="audio.ogg" controls loop>
diff --git a/browser/base/content/test/general/file_mixedContentFramesOnHttp.html b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html
new file mode 100644
index 000000000..3bd16aea5
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1182551</title>
+</head>
+<body>
+ <p>Test for Bug 1182551. This is an HTTP top level page. We include an HTTPS iframe that loads mixed passive content.</p>
+ <iframe src="https://example.org/browser/browser/base/content/test/general/file_mixedPassiveContent.html"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload.html b/browser/base/content/test/general/file_mixedContentFromOnunload.html
new file mode 100644
index 000000000..fb28a2889
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFromOnunload.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=947079
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 947079</title>
+</head>
+<body>
+ <p>Test for Bug 947079</p>
+ <script>
+ window.addEventListener('unload', function() {
+ new Image().src = 'http://mochi.test:8888/tests/image/test/mochitest/blue.png';
+ });
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html b/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html
new file mode 100644
index 000000000..1d027b036
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 1 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079
+Page with no insecure subresources
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 947079</title>
+</head>
+<body>
+ <p>There are no insecure resource loads on this page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html b/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html
new file mode 100644
index 000000000..4813337cc
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 2 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079
+Page with an insecure image load
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 947079</title>
+</head>
+<body>
+ <p>Page with http image load</p>
+ <img src="http://test2.example.com/tests/image/test/mochitest/blue.png">
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedPassiveContent.html b/browser/base/content/test/general/file_mixedPassiveContent.html
new file mode 100644
index 000000000..a60ac94e8
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedPassiveContent.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551
+-->
+<head>
+ <meta charset="utf-8">
+ <title>HTTPS page with HTTP image</title>
+</head>
+<body>
+ <img src="http://mochi.test:8888/tests/image/test/mochitest/blue.png">
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_trackingUI_6.html b/browser/base/content/test/general/file_trackingUI_6.html
new file mode 100644
index 000000000..52e1ae63f
--- /dev/null
+++ b/browser/base/content/test/general/file_trackingUI_6.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Testing the shield from fetch and XHR</title>
+</head>
+<body>
+ <p>Hello there!</p>
+ <script type="application/javascript; version=1.8">
+ function test_fetch() {
+ let url = "http://trackertest.org/browser/browser/base/content/test/general/file_trackingUI_6.js";
+ return fetch(url);
+ }
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_trackingUI_6.js b/browser/base/content/test/general/file_trackingUI_6.js
new file mode 100644
index 000000000..f7ac687cf
--- /dev/null
+++ b/browser/base/content/test/general/file_trackingUI_6.js
@@ -0,0 +1,2 @@
+/* Some code goes here! */
+void 0;
diff --git a/browser/base/content/test/general/file_trackingUI_6.js^headers^ b/browser/base/content/test/general/file_trackingUI_6.js^headers^
new file mode 100644
index 000000000..cb762eff8
--- /dev/null
+++ b/browser/base/content/test/general/file_trackingUI_6.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/browser/base/content/test/general/file_with_favicon.html b/browser/base/content/test/general/file_with_favicon.html
new file mode 100644
index 000000000..0702b4aab
--- /dev/null
+++ b/browser/base/content/test/general/file_with_favicon.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bugs with favicons</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_generic_favicon.ico">
+</head>
+<body>
+ Test file for bugs with favicons
+</body>
+</html>
diff --git a/browser/base/content/test/general/fxa_profile_handler.sjs b/browser/base/content/test/general/fxa_profile_handler.sjs
new file mode 100644
index 000000000..7160b76d0
--- /dev/null
+++ b/browser/base/content/test/general/fxa_profile_handler.sjs
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is basically an echo server!
+// We just grab responseStatus and responseBody query params!
+
+function reallyHandleRequest(request, response) {
+ var query = "?" + request.queryString;
+
+ var responseStatus = 200;
+ var match = /responseStatus=([^&]*)/.exec(query);
+ if (match) {
+ responseStatus = parseInt(match[1]);
+ }
+
+ var responseBody = "";
+ match = /responseBody=([^&]*)/.exec(query);
+ if (match) {
+ responseBody = decodeURIComponent(match[1]);
+ }
+
+ response.setStatusLine("1.0", responseStatus, "OK");
+ response.write(responseBody);
+}
+
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 500, "NotOK");
+ response.write("Error handling request: " + e);
+ }
+}
diff --git a/browser/base/content/test/general/gZipOfflineChild.cacheManifest b/browser/base/content/test/general/gZipOfflineChild.cacheManifest
new file mode 100644
index 000000000..ae0545d12
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+gZipOfflineChild.html
diff --git a/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/gZipOfflineChild.html b/browser/base/content/test/general/gZipOfflineChild.html
new file mode 100644
index 000000000..ea2caa125
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.html
Binary files differ
diff --git a/browser/base/content/test/general/gZipOfflineChild.html^headers^ b/browser/base/content/test/general/gZipOfflineChild.html^headers^
new file mode 100644
index 000000000..4204d8601
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/browser/base/content/test/general/gZipOfflineChild_uncompressed.html b/browser/base/content/test/general/gZipOfflineChild_uncompressed.html
new file mode 100644
index 000000000..4ab8f8d5e
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild_uncompressed.html
@@ -0,0 +1,21 @@
+<html manifest="gZipOfflineChild.cacheManifest">
+<head>
+ <!-- This file is gzipped to create gZipOfflineChild.html -->
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success, "*");
+}
+
+applicationCache.oncached = function() { finish("oncache"); }
+applicationCache.onnoupdate = function() { finish("onupdate"); }
+applicationCache.onerror = function() { finish("onerror"); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/head.js b/browser/base/content/test/general/head.js
new file mode 100644
index 000000000..6c28615fe
--- /dev/null
+++ b/browser/base/content/test/general/head.js
@@ -0,0 +1,1069 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler",
+ "resource:///modules/ContentCrashHandlers.jsm");
+
+/**
+ * Wait for a <notification> to be closed then call the specified callback.
+ */
+function waitForNotificationClose(notification, cb) {
+ let parent = notification.parentNode;
+
+ let observer = new MutationObserver(function onMutatations(mutations) {
+ for (let mutation of mutations) {
+ for (let i = 0; i < mutation.removedNodes.length; i++) {
+ let node = mutation.removedNodes.item(i);
+ if (node != notification) {
+ continue;
+ }
+ observer.disconnect();
+ cb();
+ }
+ }
+ });
+ observer.observe(parent, {childList: true});
+}
+
+function closeAllNotifications () {
+ let notificationBox = document.getElementById("global-notificationbox");
+
+ if (!notificationBox || !notificationBox.currentNotification) {
+ return Promise.resolve();
+ }
+
+ let deferred = Promise.defer();
+ for (let notification of notificationBox.allNotifications) {
+ waitForNotificationClose(notification, function () {
+ if (notificationBox.allNotifications.length === 0) {
+ deferred.resolve();
+ }
+ });
+ notification.close();
+ }
+
+ return deferred.promise;
+}
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
+
+function updateTabContextMenu(tab, onOpened) {
+ let menu = document.getElementById("tabContextMenu");
+ if (!tab)
+ tab = gBrowser.selectedTab;
+ var evt = new Event("");
+ tab.dispatchEvent(evt);
+ menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
+ is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
+ const onFinished = () => menu.hidePopup();
+ if (onOpened) {
+ return Task.spawn(function*() {
+ yield onOpened();
+ onFinished();
+ });
+ }
+ onFinished();
+ return Promise.resolve();
+}
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+ if (!aBrowserWin)
+ aBrowserWin = window;
+
+ aBrowserWin.gCustomizeMode.enter();
+
+ aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
+ executeSoon(function() {
+ aCallback(aBrowserWin)
+ });
+ });
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+ aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
+ executeSoon(aCallback);
+ });
+
+ aBrowserWin.gCustomizeMode.exit();
+}
+
+function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
+ retryTimes = typeof retryTimes !== 'undefined' ? retryTimes : 30;
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= retryTimes) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+function promiseWaitForCondition(aConditionFn) {
+ let deferred = Promise.defer();
+ waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+ return deferred.promise;
+}
+
+function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) {
+ return new Promise((resolve) => {
+ function listener(event) {
+ info("Saw " + eventName);
+ object.removeEventListener(eventName, listener, capturing, chrome);
+ resolve(event);
+ }
+
+ info("Waiting for " + eventName);
+ object.addEventListener(eventName, listener, capturing, chrome);
+ });
+}
+
+/**
+ * Allows setting focus on a window, and waiting for that window to achieve
+ * focus.
+ *
+ * @param aWindow
+ * The window to focus and wait for.
+ *
+ * @return {Promise}
+ * @resolves When the window is focused.
+ * @rejects Never.
+ */
+function promiseWaitForFocus(aWindow) {
+ return new Promise((resolve) => {
+ waitForFocus(resolve, aWindow);
+ });
+}
+
+function getTestPlugin(aName) {
+ var pluginName = aName || "Test Plug-in";
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (var i = 0; i < tags.length; i++) {
+ if (tags[i].name == pluginName)
+ return tags[i];
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var plugin = getTestPlugin(pluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = newEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPlugin(pluginName).enabledState = oldEnabledState;
+ });
+}
+
+function pushPrefs(...aPrefs) {
+ let deferred = Promise.defer();
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
+ return deferred.promise;
+}
+
+function updateBlocklist(aCallback) {
+ var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ var observer = function() {
+ Services.obs.removeObserver(observer, "blocklist-updated");
+ SimpleTest.executeSoon(aCallback);
+ };
+ Services.obs.addObserver(observer, "blocklist-updated", false);
+ blocklistNotifier.notify(null);
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL, aCallback) {
+ if (!_originalTestBlocklistURL)
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ updateBlocklist(aCallback);
+}
+
+function resetBlocklist() {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+}
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let win = OpenBrowserWindow(aOptions);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ aCallback(win);
+ }, false);
+}
+
+function promiseWindowWillBeClosed(win) {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver(function observe(subject, topic) {
+ if (subject == win) {
+ Services.obs.removeObserver(observe, topic);
+ resolve();
+ }
+ }, "domwindowclosed", false);
+ });
+}
+
+function promiseWindowClosed(win) {
+ let promise = promiseWindowWillBeClosed(win);
+ win.close();
+ return promise;
+}
+
+function promiseOpenAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
+ let deferred = Promise.defer();
+ let win = OpenBrowserWindow(aOptions);
+ if (aWaitForDelayedStartup) {
+ Services.obs.addObserver(function onDS(aSubject, aTopic, aData) {
+ if (aSubject != win) {
+ return;
+ }
+ Services.obs.removeObserver(onDS, "browser-delayed-startup-finished");
+ deferred.resolve(win);
+ }, "browser-delayed-startup-finished", false);
+
+ } else {
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ deferred.resolve(win);
+ });
+ }
+ return deferred.promise;
+}
+
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ * Function to be called when done.
+ * @param aScope
+ * Scope for the callback.
+ * @param aArguments
+ * Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ * a write lock. Since all statements on the same connection are
+ * serialized, the end of this write operation means that all writes are
+ * complete. Note that WAL makes so that writers don't block readers, but
+ * this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments) {
+ let scope = aScope || this;
+ let args = aArguments || [];
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+ begin.executeAsync();
+ begin.finalize();
+
+ let commit = db.createAsyncStatement("COMMIT");
+ commit.executeAsync({
+ handleResult: function() {},
+ handleError: function() {},
+ handleCompletion: function(aReason) {
+ aCallback.apply(scope, args);
+ }
+ });
+ commit.finalize();
+}
+
+/**
+ * Asynchronously check a url is visited.
+
+ * @param aURI The URI.
+ * @param aExpectedValue The expected value.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI, aExpectedValue) {
+ let deferred = Promise.defer();
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(unused, aIsVisited) {
+ deferred.resolve(aIsVisited);
+ });
+
+ return deferred.promise;
+}
+
+function whenNewTabLoaded(aWindow, aCallback) {
+ aWindow.BrowserOpenTab();
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ if (browser.contentDocument.readyState === "complete") {
+ aCallback();
+ return;
+ }
+
+ whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback);
+}
+
+function whenTabLoaded(aTab, aCallback) {
+ promiseTabLoadEvent(aTab).then(aCallback);
+}
+
+function promiseTabLoaded(aTab) {
+ let deferred = Promise.defer();
+ whenTabLoaded(aTab, deferred.resolve);
+ return deferred.promise;
+}
+
+/**
+ * Ensures that the specified URIs are either cleared or not.
+ *
+ * @param aURIs
+ * Array of page URIs
+ * @param aShouldBeCleared
+ * True if each visit to the URI should be cleared, false otherwise
+ */
+function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
+ let deferred = Promise.defer();
+ let callbackCount = 0;
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ function callbackDone() {
+ if (++callbackCount == aURIs.length)
+ deferred.resolve();
+ }
+ aURIs.forEach(function (aURI) {
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(uri, isVisited) {
+ is(isVisited, !aShouldBeCleared,
+ "history visit " + uri.spec + " should " + niceStr + " exist");
+ callbackDone();
+ });
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Waits for the next top-level document load in the current browser. The URI
+ * of the document is compared against aExpectedURL. The load is then stopped
+ * before it actually starts.
+ *
+ * @param aExpectedURL
+ * The URL of the document that is expected to load.
+ * @param aStopFromProgressListener
+ * Whether to cancel the load directly from the progress listener. Defaults to true.
+ * If you're using this method to avoid hitting the network, you want the default (true).
+ * However, the browser UI will behave differently for loads stopped directly from
+ * the progress listener (effectively in the middle of a call to loadURI) and so there
+ * are cases where you may want to avoid stopping the load directly from within the
+ * progress listener callback.
+ * @return promise
+ */
+function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) {
+ function content_script(contentStopFromProgressListener) {
+ let { interfaces: Ci, utils: Cu } = Components;
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ let wp = docShell.QueryInterface(Ci.nsIWebProgress);
+
+ function stopContent(now, uri) {
+ if (now) {
+ /* Hammer time. */
+ content.stop();
+
+ /* Let the parent know we're done. */
+ sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri });
+ } else {
+ setTimeout(stopContent.bind(null, true, uri), 0);
+ }
+ }
+
+ let progressListener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
+
+ if (webProgress.isTopLevel &&
+ flags & Ci.nsIWebProgressListener.STATE_START) {
+ wp.removeProgressListener(progressListener);
+
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
+
+ stopContent(contentStopFromProgressListener, chan.originalURI.spec);
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
+ };
+ wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
+
+ /**
+ * As |this| is undefined and we can't extend |docShell|, adding an unload
+ * event handler is the easiest way to ensure the weakly referenced
+ * progress listener is kept alive as long as necessary.
+ */
+ addEventListener("unload", function () {
+ try {
+ wp.removeProgressListener(progressListener);
+ } catch (e) { /* Will most likely fail. */ }
+ });
+ }
+
+ return new Promise((resolve, reject) => {
+ function complete({ data }) {
+ is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded");
+ mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ resolve();
+ }
+
+ let mm = aBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
+ mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
+ });
+}
+
+/**
+ * Waits for the next load to complete in any browser or the given browser.
+ * If a <tabbrowser> is given it waits for a load in any of its browsers.
+ *
+ * @return promise
+ */
+function waitForDocLoadComplete(aBrowser=gBrowser) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+ Ci.nsIWebProgressListener.STATE_STOP;
+ info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
+
+ // When a load needs to be retargetted to a new process it is cancelled
+ // with NS_BINDING_ABORTED so ignore that case
+ if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
+ aBrowser.removeProgressListener(this);
+ waitForDocLoadComplete.listeners.delete(this);
+
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ info("Browser loaded " + chan.originalURI.spec);
+ resolve();
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+ };
+ aBrowser.addProgressListener(listener);
+ waitForDocLoadComplete.listeners.add(listener);
+ info("Waiting for browser load");
+ });
+}
+
+// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
+// they're not GC'ed before we saw the page load.
+waitForDocLoadComplete.listeners = new Set();
+registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
+
+var FullZoomHelper = {
+
+ selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
+ if (!tab)
+ throw new Error("tab must be given.");
+ if (gBrowser.selectedTab == tab)
+ return Promise.resolve();
+
+ return Promise.all([BrowserTestUtils.switchTab(gBrowser, tab),
+ this.waitForLocationChange()]);
+ },
+
+ removeTabAndWaitForLocationChange: function removeTabAndWaitForLocationChange(tab) {
+ tab = tab || gBrowser.selectedTab;
+ let selected = gBrowser.selectedTab == tab;
+ gBrowser.removeTab(tab);
+ if (selected)
+ return this.waitForLocationChange();
+ return Promise.resolve();
+ },
+
+ waitForLocationChange: function waitForLocationChange() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function obs(subj, topic, data) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }, "browser-fullZoom:location-change", false);
+ });
+ },
+
+ load: function load(tab, url) {
+ return new Promise(resolve => {
+ let didLoad = false;
+ let didZoom = false;
+
+ promiseTabLoadEvent(tab).then(event => {
+ didLoad = true;
+ if (didZoom)
+ resolve();
+ }, true);
+
+ this.waitForLocationChange().then(function () {
+ didZoom = true;
+ if (didLoad)
+ resolve();
+ });
+
+ tab.linkedBrowser.loadURI(url);
+ });
+ },
+
+ zoomTest: function zoomTest(tab, val, msg) {
+ is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
+ },
+
+ enlarge: function enlarge() {
+ return new Promise(resolve => FullZoom.enlarge(resolve));
+ },
+
+ reduce: function reduce() {
+ return new Promise(resolve => FullZoom.reduce(resolve));
+ },
+
+ reset: function reset() {
+ return FullZoom.reset();
+ },
+
+ BACK: 0,
+ FORWARD: 1,
+ navigate: function navigate(direction) {
+ return new Promise(resolve => {
+ let didPs = false;
+ let didZoom = false;
+
+ gBrowser.addEventListener("pageshow", function listener(event) {
+ gBrowser.removeEventListener("pageshow", listener, true);
+ didPs = true;
+ if (didZoom)
+ resolve();
+ }, true);
+
+ if (direction == this.BACK)
+ gBrowser.goBack();
+ else if (direction == this.FORWARD)
+ gBrowser.goForward();
+
+ this.waitForLocationChange().then(function () {
+ didZoom = true;
+ if (didPs)
+ resolve();
+ });
+ });
+ },
+
+ failAndContinue: function failAndContinue(func) {
+ return function (err) {
+ ok(false, err);
+ func();
+ };
+ },
+};
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url)
+{
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url)
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+ return loaded;
+}
+
+/**
+ * Returns a Promise that resolves once a new tab has been opened in
+ * a xul:tabbrowser.
+ *
+ * @param aTabBrowser
+ * The xul:tabbrowser to monitor for a new tab.
+ * @return {Promise}
+ * Resolved when the new tab has been opened.
+ * @resolves to the TabOpen event that was fired.
+ * @rejects Never.
+ */
+function waitForNewTabEvent(aTabBrowser) {
+ return promiseWaitForEvent(aTabBrowser.tabContainer, "TabOpen");
+}
+
+/**
+ * Test the state of the identity box and control center to make
+ * sure they are correctly showing the expected mixed content states.
+ *
+ * @note The checks are done synchronously, but new code should wait on the
+ * returned Promise object to ensure the identity panel has closed.
+ * Bug 1221114 is filed to fix the existing code.
+ *
+ * @param tabbrowser
+ * @param Object states
+ * MUST include the following properties:
+ * {
+ * activeLoaded: true|false,
+ * activeBlocked: true|false,
+ * passiveLoaded: true|false,
+ * }
+ *
+ * @return {Promise}
+ * @resolves When the operation has finished and the identity panel has closed.
+ */
+function assertMixedContentBlockingState(tabbrowser, states = {}) {
+ if (!tabbrowser || !("activeLoaded" in states) ||
+ !("activeBlocked" in states) || !("passiveLoaded" in states)) {
+ throw new Error("assertMixedContentBlockingState requires a browser and a states object");
+ }
+
+ let {passiveLoaded, activeLoaded, activeBlocked} = states;
+ let {gIdentityHandler} = tabbrowser.ownerGlobal;
+ let doc = tabbrowser.ownerDocument;
+ let identityBox = gIdentityHandler._identityBox;
+ let classList = identityBox.classList;
+ let connectionIcon = doc.getElementById("connection-icon");
+ let connectionIconImage = tabbrowser.ownerGlobal.getComputedStyle(connectionIcon).
+ getPropertyValue("list-style-image");
+
+ let stateSecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
+ let stateBroken = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
+ let stateInsecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_INSECURE;
+ let stateActiveBlocked = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+ let stateActiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
+ let statePassiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
+
+ is(activeBlocked, !!stateActiveBlocked, "Expected state for activeBlocked matches UI state");
+ is(activeLoaded, !!stateActiveLoaded, "Expected state for activeLoaded matches UI state");
+ is(passiveLoaded, !!statePassiveLoaded, "Expected state for passiveLoaded matches UI state");
+
+ if (stateInsecure) {
+ // HTTP request, there should be no MCB classes for the identity box and the non secure icon
+ // should always be visible regardless of MCB state.
+ ok(classList.contains("unknownIdentity"), "unknownIdentity on HTTP page");
+ is_element_hidden(connectionIcon);
+
+ ok(!classList.contains("mixedActiveContent"), "No MCB icon on HTTP page");
+ ok(!classList.contains("mixedActiveBlocked"), "No MCB icon on HTTP page");
+ ok(!classList.contains("mixedDisplayContent"), "No MCB icon on HTTP page");
+ ok(!classList.contains("mixedDisplayContentLoadedActiveBlocked"), "No MCB icon on HTTP page");
+ } else {
+ // Make sure the identity box UI has the correct mixedcontent states and icons
+ is(classList.contains("mixedActiveContent"), activeLoaded,
+ "identityBox has expected class for activeLoaded");
+ is(classList.contains("mixedActiveBlocked"), activeBlocked && !passiveLoaded,
+ "identityBox has expected class for activeBlocked && !passiveLoaded");
+ is(classList.contains("mixedDisplayContent"), passiveLoaded && !(activeLoaded || activeBlocked),
+ "identityBox has expected class for passiveLoaded && !(activeLoaded || activeBlocked)");
+ is(classList.contains("mixedDisplayContentLoadedActiveBlocked"), passiveLoaded && activeBlocked,
+ "identityBox has expected class for passiveLoaded && activeBlocked");
+
+ is_element_visible(connectionIcon);
+ if (activeLoaded) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg#icon\")",
+ "Using active loaded icon");
+ }
+ if (activeBlocked && !passiveLoaded) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-secure.svg\")",
+ "Using active blocked icon");
+ }
+ if (passiveLoaded && !(activeLoaded || activeBlocked)) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using passive loaded icon");
+ }
+ if (passiveLoaded && activeBlocked) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using active blocked and passive loaded icon");
+ }
+ }
+
+ // Make sure the identity popup has the correct mixedcontent states
+ gIdentityHandler._identityBox.click();
+ let popupAttr = doc.getElementById("identity-popup").getAttribute("mixedcontent");
+ let bodyAttr = doc.getElementById("identity-popup-securityView-body").getAttribute("mixedcontent");
+
+ is(popupAttr.includes("active-loaded"), activeLoaded,
+ "identity-popup has expected attr for activeLoaded");
+ is(bodyAttr.includes("active-loaded"), activeLoaded,
+ "securityView-body has expected attr for activeLoaded");
+
+ is(popupAttr.includes("active-blocked"), activeBlocked,
+ "identity-popup has expected attr for activeBlocked");
+ is(bodyAttr.includes("active-blocked"), activeBlocked,
+ "securityView-body has expected attr for activeBlocked");
+
+ is(popupAttr.includes("passive-loaded"), passiveLoaded,
+ "identity-popup has expected attr for passiveLoaded");
+ is(bodyAttr.includes("passive-loaded"), passiveLoaded,
+ "securityView-body has expected attr for passiveLoaded");
+
+ // Make sure the correct icon is visible in the Control Center.
+ // This logic is controlled with CSS, so this helps prevent regressions there.
+ let securityView = doc.getElementById("identity-popup-securityView");
+ let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView).
+ getPropertyValue("background-image");
+ let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView).
+ getPropertyValue("background-image");
+
+ if (stateInsecure) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
+ "CC using 'not secure' icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
+ "CC using 'not secure' icon");
+ }
+
+ if (stateSecure) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "CC using secure icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "CC using secure icon");
+ }
+
+ if (stateBroken) {
+ if (activeLoaded) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "CC using active loaded icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "CC using active loaded icon");
+ } else if (activeBlocked || passiveLoaded) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ } else {
+ // There is a case here with weak ciphers, but no bc tests are handling this yet.
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ }
+ }
+
+ if (activeLoaded || activeBlocked || passiveLoaded) {
+ doc.getElementById("identity-popup-security-expander").click();
+ is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
+ element => !is_hidden(element)).length, 1,
+ "The 'Learn more' link should be visible once.");
+ }
+
+ gIdentityHandler._identityPopup.hidden = true;
+
+ // Wait for the panel to be closed before continuing. The promisePopupHidden
+ // function cannot be used because it's unreliable unless promisePopupShown is
+ // also called before closing the panel. This cannot be done until all callers
+ // are made asynchronous (bug 1221114).
+ return new Promise(resolve => executeSoon(resolve));
+}
+
+function is_hidden(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ if (style.display == "-moz-popup")
+ return ["hiding", "closed"].indexOf(element.state) != -1;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_hidden(element.parentNode);
+
+ return false;
+}
+
+function is_visible(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return false;
+ if (style.visibility != "visible")
+ return false;
+ if (style.display == "-moz-popup" && element.state != "open")
+ return false;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_visible(element.parentNode);
+
+ return true;
+}
+
+function is_element_visible(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_visible(element), msg || "Element should be visible");
+}
+
+function is_element_hidden(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(element), msg || "Element should be hidden");
+}
+
+function promisePopupEvent(popup, eventSuffix) {
+ let endState = {shown: "open", hidden: "closed"}[eventSuffix];
+
+ if (popup.state == endState)
+ return Promise.resolve();
+
+ let eventType = "popup" + eventSuffix;
+ let deferred = Promise.defer();
+ popup.addEventListener(eventType, function onPopupShown(event) {
+ popup.removeEventListener(eventType, onPopupShown);
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function promisePopupShown(popup) {
+ return promisePopupEvent(popup, "shown");
+}
+
+function promisePopupHidden(popup) {
+ return promisePopupEvent(popup, "hidden");
+}
+
+function promiseNotificationShown(notification) {
+ let win = notification.browser.ownerGlobal;
+ if (win.PopupNotifications.panel.state == "open") {
+ return Promise.resolve();
+ }
+ let panelPromise = promisePopupShown(win.PopupNotifications.panel);
+ notification.reshow();
+ return panelPromise;
+}
+
+/**
+ * Allows waiting for an observer notification once.
+ *
+ * @param aTopic
+ * Notification topic to observe.
+ *
+ * @return {Promise}
+ * @resolves An object with subject and data properties from the observed
+ * notification.
+ * @rejects Never.
+ */
+function promiseTopicObserved(aTopic)
+{
+ return new Promise((resolve) => {
+ Services.obs.addObserver(
+ function PTO_observe(aSubject, aTopic2, aData) {
+ Services.obs.removeObserver(PTO_observe, aTopic2);
+ resolve({subject: aSubject, data: aData});
+ }, aTopic, false);
+ });
+}
+
+function promiseNewSearchEngine(basename) {
+ return new Promise((resolve, reject) => {
+ info("Waiting for engine to be added: " + basename);
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ registerCleanupFunction(() => Services.search.removeEngine(engine));
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ Assert.ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+}
+
+// Compares the security state of the page with what is expected
+function isSecurityState(expectedState) {
+ let ui = gTestBrowser.securityUI;
+ if (!ui) {
+ ok(false, "No security UI to get the security state");
+ return;
+ }
+
+ const wpl = Components.interfaces.nsIWebProgressListener;
+
+ // determine the security state
+ let isSecure = ui.state & wpl.STATE_IS_SECURE;
+ let isBroken = ui.state & wpl.STATE_IS_BROKEN;
+ let isInsecure = ui.state & wpl.STATE_IS_INSECURE;
+
+ let actualState;
+ if (isSecure && !(isBroken || isInsecure)) {
+ actualState = "secure";
+ } else if (isBroken && !(isSecure || isInsecure)) {
+ actualState = "broken";
+ } else if (isInsecure && !(isSecure || isBroken)) {
+ actualState = "insecure";
+ } else {
+ actualState = "unknown";
+ }
+
+ is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + ".");
+}
+
+/**
+ * Resolves when a bookmark with the given uri is added.
+ */
+function promiseOnBookmarkItemAdded(aExpectedURI) {
+ return new Promise((resolve, reject) => {
+ let bookmarksObserver = {
+ onItemAdded: function (aItemId, aFolderId, aIndex, aItemType, aURI) {
+ info("Added a bookmark to " + aURI.spec);
+ PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
+ if (aURI.equals(aExpectedURI)) {
+ resolve();
+ }
+ else {
+ reject(new Error("Added an unexpected bookmark"));
+ }
+ },
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemRemoved: function () {},
+ onItemChanged: function () {},
+ onItemVisited: function () {},
+ onItemMoved: function () {},
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver,
+ ])
+ };
+ info("Waiting for a bookmark to be added");
+ PlacesUtils.bookmarks.addObserver(bookmarksObserver, false);
+ });
+}
+
+function promiseErrorPageLoaded(browser) {
+ return new Promise(resolve => {
+ browser.addEventListener("DOMContentLoaded", function onLoad() {
+ browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
+ resolve();
+ }, false, true);
+ });
+}
+
+function* loadBadCertPage(url) {
+ const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul";
+ let exceptionDialogResolved = new Promise(function(resolve) {
+ // When the certificate exception dialog has opened, click the button to add
+ // an exception.
+ let certExceptionDialogObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "cert-exception-ui-ready") {
+ Services.obs.removeObserver(this, "cert-exception-ui-ready");
+ let certExceptionDialog = getCertExceptionDialog(EXCEPTION_DIALOG_URI);
+ ok(certExceptionDialog, "found exception dialog");
+ executeSoon(function() {
+ certExceptionDialog.documentElement.getButton("extra1").click();
+ resolve();
+ });
+ }
+ }
+ };
+
+ Services.obs.addObserver(certExceptionDialogObserver,
+ "cert-exception-ui-ready", false);
+ });
+
+ let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
+ yield loaded;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ content.document.getElementById("exceptionDialogButton").click();
+ });
+ yield exceptionDialogResolved;
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+}
+
+// Utility function to get a handle on the certificate exception dialog.
+// Modified from toolkit/components/passwordmgr/test/prompt_common.js
+function getCertExceptionDialog(aLocation) {
+ let enumerator = Services.wm.getXULWindowEnumerator(null);
+
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ let windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+ let containedDocShells = windowDocShell.getDocShellEnumerator(
+ Ci.nsIDocShellTreeItem.typeChrome,
+ Ci.nsIDocShell.ENUMERATE_FORWARDS);
+ while (containedDocShells.hasMoreElements()) {
+ // Get the corresponding document for this docshell
+ let childDocShell = containedDocShells.getNext();
+ let childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+ .contentViewer
+ .DOMDocument;
+
+ if (childDoc.location.href == aLocation) {
+ return childDoc;
+ }
+ }
+ }
+ return undefined;
+}
+
+function setupRemoteClientsFixture(fixture) {
+ let oldRemoteClientsGetter =
+ Object.getOwnPropertyDescriptor(gFxAccounts, "remoteClients").get;
+
+ Object.defineProperty(gFxAccounts, "remoteClients", {
+ get: function() { return fixture; }
+ });
+ return oldRemoteClientsGetter;
+}
+
+function restoreRemoteClients(getter) {
+ Object.defineProperty(gFxAccounts, "remoteClients", {
+ get: getter
+ });
+}
+
+function* openMenuItemSubmenu(id) {
+ let menuPopup = document.getElementById(id).menupopup;
+ let menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown");
+ menuPopup.showPopup();
+ yield menuPopupPromise;
+}
diff --git a/browser/base/content/test/general/head_plain.js b/browser/base/content/test/general/head_plain.js
new file mode 100644
index 000000000..3796c7d2b
--- /dev/null
+++ b/browser/base/content/test/general/head_plain.js
@@ -0,0 +1,27 @@
+
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var plugin = getTestPlugin(pluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = newEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPlugin(pluginName).enabledState = oldEnabledState;
+ });
+}
diff --git a/browser/base/content/test/general/healthreport_pingData.js b/browser/base/content/test/general/healthreport_pingData.js
new file mode 100644
index 000000000..1737baba1
--- /dev/null
+++ b/browser/base/content/test/general/healthreport_pingData.js
@@ -0,0 +1,17 @@
+var TEST_PINGS = [
+ {
+ type: "test-telemetryArchive-1",
+ payload: { foo: "bar" },
+ date: new Date(2010, 1, 1, 10, 0, 0),
+ },
+ {
+ type: "test-telemetryArchive-2",
+ payload: { x: { y: "z"} },
+ date: new Date(2010, 1, 1, 11, 0, 0),
+ },
+ {
+ type: "test-telemetryArchive-3",
+ payload: { moo: "meh" },
+ date: new Date(2010, 1, 1, 12, 0, 0),
+ },
+];
diff --git a/browser/base/content/test/general/healthreport_testRemoteCommands.html b/browser/base/content/test/general/healthreport_testRemoteCommands.html
new file mode 100644
index 000000000..7978914f2
--- /dev/null
+++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html
@@ -0,0 +1,243 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+<script type="application/javascript;version=1.7"
+ src="healthreport_pingData.js">
+</script>
+<script type="application/javascript;version=1.7">
+
+function init() {
+ window.addEventListener("message", doTest, false);
+ doTest();
+}
+
+function checkSubmissionValue(payload, expectedValue) {
+ return payload.enabled == expectedValue;
+}
+
+function isArray(arg) {
+ return Object.prototype.toString.call(arg) === '[object Array]';
+}
+
+function writeDiagnostic(text) {
+ let node = document.createTextNode(text);
+ let br = document.createElement("br");
+ document.body.appendChild(node);
+ document.body.appendChild(br);
+}
+
+function validateCurrentTelemetryEnvironment(data) {
+ // Simple check for now: check that the received object has the expected
+ // top-level properties.
+ const expectedKeys = ["profile", "settings", "system", "build", "partner", "addons"];
+ return expectedKeys.every(key => (key in data));
+}
+
+function validateCurrentTelemetryPingData(ping) {
+ // Simple check for now: check that the received object has the expected
+ // top-level properties and that the type and reason match.
+ const expectedKeys = ["environment", "clientId", "payload", "application",
+ "version", "type", "id"];
+ return expectedKeys.every(key => (key in ping)) &&
+ (ping.type == "main") &&
+ ("info" in ping.payload) &&
+ ("reason" in ping.payload.info) &&
+ (ping.payload.info.reason == "gather-subsession-payload");
+}
+
+function validateTelemetryPingList(list) {
+ if (!isArray(list)) {
+ console.log("Telemetry ping list is not an array.");
+ return false;
+ }
+
+ // Telemetry may generate other pings (e.g. "deletion" pings), so filter those
+ // out.
+ const TEST_TYPES_REGEX = /^test-telemetryArchive/;
+ list = list.filter(p => TEST_TYPES_REGEX.test(p.type));
+
+ if (list.length != TEST_PINGS.length) {
+ console.log("Telemetry ping length is not correct.");
+ return false;
+ }
+
+ let valid = true;
+ for (let i=0; i<list.length; ++i) {
+ let received = list[i];
+ let expected = TEST_PINGS[i];
+ if (received.type != expected.type ||
+ received.timestampCreated != expected.date.getTime()) {
+ writeDiagnostic("Telemetry ping " + i + " does not match.");
+ writeDiagnostic("Expected: " + JSON.stringify(expected));
+ writeDiagnostic("Received: " + JSON.stringify(received));
+ valid = false;
+ } else {
+ writeDiagnostic("Telemetry ping " + i + " matches.");
+ }
+ }
+
+ return true;
+}
+
+function validateTelemetryPingData(expected, received) {
+ const receivedDate = new Date(received.creationDate);
+ if (received.id != expected.id ||
+ received.type != expected.type ||
+ receivedDate.getTime() != expected.date.getTime()) {
+ writeDiagnostic("Telemetry ping data for " + expected.id + " doesn't match.");
+ writeDiagnostic("Expected: " + JSON.stringify(expected));
+ writeDiagnostic("Received: " + JSON.stringify(received));
+ return false;
+ }
+
+ writeDiagnostic("Telemetry ping data for " + expected.id + " matched.");
+ return true;
+}
+
+var tests = [
+{
+ info: "Checking initial value is enabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying disabling works",
+ event: "DisableDataSubmission",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, false);
+ },
+},
+{
+ info: "Verifying we're still disabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, false);
+ },
+},
+{
+ info: "Verifying that we can get the current ping data while submission is disabled",
+ event: "RequestCurrentPingData",
+ payloadType: "telemetry-current-ping-data",
+ validateResponse: function(payload) {
+ return validateCurrentTelemetryPingData(payload);
+ },
+},
+{
+ info: "Verifying enabling works",
+ event: "EnableDataSubmission",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying we're still re-enabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying that we can get the current Telemetry environment data",
+ event: "RequestCurrentEnvironment",
+ payloadType: "telemetry-current-environment-data",
+ validateResponse: function(payload) {
+ return validateCurrentTelemetryEnvironment(payload);
+ },
+},
+{
+ info: "Verifying that we can get the current Telemetry ping data",
+ event: "RequestCurrentPingData",
+ payloadType: "telemetry-current-ping-data",
+ validateResponse: function(payload) {
+ return validateCurrentTelemetryPingData(payload);
+ },
+},
+{
+ info: "Verifying that we get the proper Telemetry ping list",
+ event: "RequestTelemetryPingList",
+ payloadType: "telemetry-ping-list",
+ validateResponse: function(payload) {
+ // Validate the ping list
+ if (!validateTelemetryPingList(payload)) {
+ return false;
+ }
+
+ // Now that we received the ping ids, set up additional test tasks
+ // that check loading the individual pings.
+ for (let i=0; i<TEST_PINGS.length; ++i) {
+ TEST_PINGS[i].id = payload[i].id;
+ tests.push({
+ info: "Verifying that we can get the proper Telemetry ping data #" + (i + 1),
+ event: "RequestTelemetryPingData",
+ eventData: { id: TEST_PINGS[i].id },
+ payloadType: "telemetry-ping-data",
+ validateResponse: function(payload) {
+ return validateTelemetryPingData(TEST_PINGS[i], payload.pingData);
+ },
+ });
+ }
+
+ return true;
+ },
+},
+];
+
+var currentTest = -1;
+function doTest(evt) {
+ if (evt) {
+ if (currentTest < 0 || !evt.data.content)
+ return; // not yet testing
+
+ var test = tests[currentTest];
+ if (evt.data.type != test.payloadType)
+ return; // skip unrequested events
+
+ var error = JSON.stringify(evt.data.content);
+ var pass = false;
+ try {
+ pass = test.validateResponse(evt.data.content)
+ } catch (e) {}
+ reportResult(test.info, pass, error);
+ }
+ // start the next test if there are any left
+ if (tests[++currentTest])
+ sendToBrowser(tests[currentTest].event, tests[currentTest].eventData);
+ else
+ reportFinished();
+}
+
+function reportResult(info, pass, error) {
+ var data = {type: "testResult", info: info, pass: pass, error: error};
+ var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function reportFinished(cmd) {
+ var data = {type: "testsComplete", count: tests.length};
+ var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function sendToBrowser(type, eventData) {
+ eventData = eventData || {};
+ let detail = {command: type};
+ for (let key of Object.keys(eventData)) {
+ detail[key] = eventData[key];
+ }
+
+ var event = new CustomEvent("RemoteHealthReportCommand", {detail: detail, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+</script>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/browser/base/content/test/general/insecure_opener.html b/browser/base/content/test/general/insecure_opener.html
new file mode 100644
index 000000000..26ed014f6
--- /dev/null
+++ b/browser/base/content/test/general/insecure_opener.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <a id="link" target="_blank" href="https://example.com/browser/toolkit/components/passwordmgr/test/browser/form_basic.html">Click me, I'm "secure".</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/mochitest.ini b/browser/base/content/test/general/mochitest.ini
new file mode 100644
index 000000000..a07a01b87
--- /dev/null
+++ b/browser/base/content/test/general/mochitest.ini
@@ -0,0 +1,27 @@
+[DEFAULT]
+support-files =
+ audio.ogg
+ bug364677-data.xml
+ bug364677-data.xml^headers^
+ bug395533-data.txt
+ contextmenu_common.js
+ ctxmenu-image.png
+ head_plain.js
+ offlineByDefault.js
+ offlineChild.cacheManifest
+ offlineChild.cacheManifest^headers^
+ offlineChild.html
+ offlineChild2.cacheManifest
+ offlineChild2.cacheManifest^headers^
+ offlineChild2.html
+ offlineEvent.cacheManifest
+ offlineEvent.cacheManifest^headers^
+ offlineEvent.html
+ subtst_contextmenu.html
+ video.ogg
+ !/image/test/mochitest/blue.png
+
+[test_bug364677.html]
+[test_bug395533.html]
+[test_offlineNotification.html]
+skip-if = e10s # Bug 1257785
diff --git a/browser/base/content/test/general/moz.png b/browser/base/content/test/general/moz.png
new file mode 100644
index 000000000..769c63634
--- /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 000000000..6b0918941
--- /dev/null
+++ b/browser/base/content/test/general/navigating_window_with_download.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <head><title>This window will navigate while you're downloading something</title></head>
+ <body>
+ <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/offlineByDefault.js b/browser/base/content/test/general/offlineByDefault.js
new file mode 100644
index 000000000..72f7e52a0
--- /dev/null
+++ b/browser/base/content/test/general/offlineByDefault.js
@@ -0,0 +1,17 @@
+var offlineByDefault = {
+ defaultValue: false,
+ prefBranch: SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch),
+ set: function(allow) {
+ try {
+ this.defaultValue = this.prefBranch.getBoolPref("offline-apps.allow_by_default");
+ } catch (e) {
+ this.defaultValue = false
+ }
+ this.prefBranch.setBoolPref("offline-apps.allow_by_default", allow);
+ },
+ reset: function() {
+ this.prefBranch.setBoolPref("offline-apps.allow_by_default", this.defaultValue);
+ }
+}
+
+offlineByDefault.set(false);
diff --git a/browser/base/content/test/general/offlineChild.cacheManifest b/browser/base/content/test/general/offlineChild.cacheManifest
new file mode 100644
index 000000000..091fe7194
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild.html
diff --git a/browser/base/content/test/general/offlineChild.cacheManifest^headers^ b/browser/base/content/test/general/offlineChild.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/offlineChild.html b/browser/base/content/test/general/offlineChild.html
new file mode 100644
index 000000000..43f225b3b
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild.html
@@ -0,0 +1,20 @@
+<html manifest="offlineChild.cacheManifest">
+<head>
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success ? "success" : "failure", "*");
+}
+
+applicationCache.oncached = function() { finish(true); }
+applicationCache.onnoupdate = function() { finish(true); }
+applicationCache.onerror = function() { finish(false); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/offlineChild2.cacheManifest b/browser/base/content/test/general/offlineChild2.cacheManifest
new file mode 100644
index 000000000..19efe54fe
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild2.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild2.html
diff --git a/browser/base/content/test/general/offlineChild2.cacheManifest^headers^ b/browser/base/content/test/general/offlineChild2.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild2.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/offlineChild2.html b/browser/base/content/test/general/offlineChild2.html
new file mode 100644
index 000000000..ac762e759
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild2.html
@@ -0,0 +1,20 @@
+<html manifest="offlineChild2.cacheManifest">
+<head>
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success ? "success" : "failure", "*");
+}
+
+applicationCache.oncached = function() { finish(true); }
+applicationCache.onnoupdate = function() { finish(true); }
+applicationCache.onerror = function() { finish(false); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/offlineEvent.cacheManifest b/browser/base/content/test/general/offlineEvent.cacheManifest
new file mode 100644
index 000000000..091fe7194
--- /dev/null
+++ b/browser/base/content/test/general/offlineEvent.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild.html
diff --git a/browser/base/content/test/general/offlineEvent.cacheManifest^headers^ b/browser/base/content/test/general/offlineEvent.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/offlineEvent.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/offlineEvent.html b/browser/base/content/test/general/offlineEvent.html
new file mode 100644
index 000000000..f6e2494e2
--- /dev/null
+++ b/browser/base/content/test/general/offlineEvent.html
@@ -0,0 +1,9 @@
+<html manifest="offlineEvent.cacheManifest">
+<head>
+<title></title>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/offlineQuotaNotification.cacheManifest b/browser/base/content/test/general/offlineQuotaNotification.cacheManifest
new file mode 100644
index 000000000..2e210abd2
--- /dev/null
+++ b/browser/base/content/test/general/offlineQuotaNotification.cacheManifest
@@ -0,0 +1,7 @@
+CACHE MANIFEST
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# store a "large" file so an "over quota warning" will be issued - any file
+# larger than 1kb and in '_BROWSER_FILES' should be right...
+title_test.svg
diff --git a/browser/base/content/test/general/offlineQuotaNotification.html b/browser/base/content/test/general/offlineQuotaNotification.html
new file mode 100644
index 000000000..b1b91bf9e
--- /dev/null
+++ b/browser/base/content/test/general/offlineQuotaNotification.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html manifest="offlineQuotaNotification.cacheManifest">
+<head>
+ <meta charset="utf-8">
+ <title>Test offline app quota notification</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+</html>
diff --git a/browser/base/content/test/general/page_style_sample.html b/browser/base/content/test/general/page_style_sample.html
new file mode 100644
index 000000000..54cbaa9e6
--- /dev/null
+++ b/browser/base/content/test/general/page_style_sample.html
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <title>Test for page style menu</title>
+ <!-- data-state values:
+ 0: should not appear in the page style menu
+ 0-todo: should not appear in the page style menu, but does
+ 1: should appear in the page style menu
+ 2: should appear in the page style menu as the selected stylesheet -->
+ <link data-state="1" href="404.css" title="1" rel="alternate stylesheet">
+ <link data-state="0" title="2" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" title="" rel="alternate stylesheet">
+ <link data-state="1" href="404.css" title="3" rel="stylesheet alternate">
+ <link data-state="1" href="404.css" title="4" rel=" alternate stylesheet ">
+ <link data-state="1" href="404.css" title="5" rel="alternate stylesheet">
+ <link data-state="2" href="404.css" title="6" rel="stylesheet">
+ <link data-state="1" href="404.css" title="7" rel="foo stylesheet">
+ <link data-state="0" href="404.css" title="8" rel="alternate">
+ <link data-state="1" href="404.css" title="9" rel="alternate STYLEsheet">
+ <link data-state="1" href="404.css" title="10" rel="alternate stylesheet" media="">
+ <link data-state="1" href="404.css" title="11" rel="alternate stylesheet" media="all">
+ <link data-state="1" href="404.css" title="12" rel="alternate stylesheet" media="ALL ">
+ <link data-state="1" href="404.css" title="13" rel="alternate stylesheet" media="screen">
+ <link data-state="1" href="404.css" title="14" rel="alternate stylesheet" media=" Screen">
+ <link data-state="0" href="404.css" title="15" rel="alternate stylesheet" media="screen foo">
+ <link data-state="0" href="404.css" title="16" rel="alternate stylesheet" media="all screen">
+ <link data-state="0" href="404.css" title="17" rel="alternate stylesheet" media="foo bar">
+ <link data-state="1" href="404.css" title="18" rel="alternate stylesheet" media="all,screen">
+ <link data-state="1" href="404.css" title="19" rel="alternate stylesheet" media="all, screen">
+ <link data-state="0" href="404.css" title="20" rel="alternate stylesheet" media="all screen">
+ <link data-state="0" href="404.css" title="21" rel="alternate stylesheet" media="foo">
+ <link data-state="0" href="404.css" title="22" rel="alternate stylesheet" media="allscreen">
+ <link data-state="0" href="404.css" title="23" rel="alternate stylesheet" media="_all">
+ <link data-state="0" href="404.css" title="24" rel="alternate stylesheet" media="not screen">
+ <link data-state="1" href="404.css" title="25" rel="alternate stylesheet" media="only screen">
+ <link data-state="1" href="404.css" title="26" rel="alternate stylesheet" media="screen and (min-device-width: 1px)">
+ <link data-state="0" href="404.css" title="27" rel="alternate stylesheet" media="screen and (max-device-width: 1px)">
+ <style data-state="1" title="28">/* some more styles */</style>
+ </head>
+ <body></body>
+</html>
diff --git a/browser/base/content/test/general/parsingTestHelpers.jsm b/browser/base/content/test/general/parsingTestHelpers.jsm
new file mode 100644
index 000000000..69c764483
--- /dev/null
+++ b/browser/base/content/test/general/parsingTestHelpers.jsm
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["generateURIsFromDirTree"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+/* Shorthand constructors to construct an nsI(Local)File and zip reader: */
+const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", Ci.nsIFile, "initWithPath");
+const ZipReader = new Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
+
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+
+/**
+ * Returns a promise that is resolved with a list of files that have one of the
+ * extensions passed, represented by their nsIURI objects, which exist inside
+ * the directory passed.
+ *
+ * @param dir the directory which to scan for files (nsIFile)
+ * @param extensions the extensions of files we're interested in (Array).
+ */
+function generateURIsFromDirTree(dir, extensions) {
+ if (!Array.isArray(extensions)) {
+ extensions = [extensions];
+ }
+ let dirQueue = [dir.path];
+ return Task.spawn(function*() {
+ let rv = [];
+ while (dirQueue.length) {
+ let nextDir = dirQueue.shift();
+ let {subdirs, files} = yield iterateOverPath(nextDir, extensions);
+ dirQueue.push(...subdirs);
+ rv.push(...files);
+ }
+ return rv;
+ });
+}
+
+/**
+ * Uses OS.File.DirectoryIterator to asynchronously iterate over a directory.
+ * It returns a promise that is resolved with an object with two properties:
+ * - files: an array of nsIURIs corresponding to files that match the extensions passed
+ * - subdirs: an array of paths for subdirectories we need to recurse into
+ * (handled by generateURIsFromDirTree above)
+ *
+ * @param path the path to check (string)
+ * @param extensions the file extensions we're interested in.
+ */
+function iterateOverPath(path, extensions) {
+ let iterator = new OS.File.DirectoryIterator(path);
+ let parentDir = new LocalFile(path);
+ let subdirs = [];
+ let files = [];
+
+ let pathEntryIterator = (entry) => {
+ if (entry.isDir) {
+ subdirs.push(entry.path);
+ } else if (extensions.some((extension) => entry.name.endsWith(extension))) {
+ let file = parentDir.clone();
+ file.append(entry.name);
+ // the build system might leave dead symlinks hanging around, which are
+ // returned as part of the directory iterator, but don't actually exist:
+ if (file.exists()) {
+ let uriSpec = getURLForFile(file);
+ files.push(Services.io.newURI(uriSpec, null, null));
+ }
+ } else if (entry.name.endsWith(".ja") || entry.name.endsWith(".jar") ||
+ entry.name.endsWith(".zip") || entry.name.endsWith(".xpi")) {
+ let file = parentDir.clone();
+ file.append(entry.name);
+ for (let extension of extensions) {
+ let jarEntryIterator = generateEntriesFromJarFile(file, extension);
+ files.push(...jarEntryIterator);
+ }
+ }
+ };
+
+ return new Promise((resolve, reject) => {
+ Task.spawn(function* () {
+ try {
+ // Iterate through the directory
+ yield iterator.forEach(pathEntryIterator);
+ resolve({files: files, subdirs: subdirs});
+ } catch (ex) {
+ reject(ex);
+ } finally {
+ iterator.close();
+ }
+ });
+ });
+}
+
+/* Helper function to generate a URI spec (NB: not an nsIURI yet!)
+ * given an nsIFile object */
+function getURLForFile(file) {
+ let fileHandler = Services.io.getProtocolHandler("file");
+ fileHandler = fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
+ return fileHandler.getURLSpecFromActualFile(file);
+}
+
+/**
+ * A generator that generates nsIURIs for particular files found in jar files
+ * like omni.ja.
+ *
+ * @param jarFile an nsIFile object for the jar file that needs checking.
+ * @param extension the extension we're interested in.
+ */
+function* generateEntriesFromJarFile(jarFile, extension) {
+ let zr = new ZipReader(jarFile);
+ let entryEnumerator = zr.findEntries("*" + extension + "$");
+
+ const kURIStart = getURLForFile(jarFile);
+ while (entryEnumerator.hasMore()) {
+ let entry = entryEnumerator.getNext();
+ // Ignore the JS cache which is stored in omni.ja
+ if (entry.startsWith("jsloader") || entry.startsWith("jssubloader")) {
+ continue;
+ }
+ let entryURISpec = "jar:" + kURIStart + "!/" + entry;
+ yield Services.io.newURI(entryURISpec, null, null);
+ }
+ zr.close();
+}
+
+
diff --git a/browser/base/content/test/general/permissions.html b/browser/base/content/test/general/permissions.html
new file mode 100644
index 000000000..46436a006
--- /dev/null
+++ b/browser/base/content/test/general/permissions.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <!-- This page could eventually request permissions from content
+ and make sure that chrome responds appropriately -->
+ <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/pinning_headers.sjs b/browser/base/content/test/general/pinning_headers.sjs
new file mode 100644
index 000000000..51496183a
--- /dev/null
+++ b/browser/base/content/test/general/pinning_headers.sjs
@@ -0,0 +1,23 @@
+const INVALIDPIN1 = "pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\";";
+const INVALIDPIN2 = "pin-sha256=\"AAAAAAAAAAAAAAAAAAAAAAAAAj0e1Md7GkYYkVoZWmM=\";";
+const VALIDPIN = "pin-sha256=\"hXweb81C3HnmM2Ai1dnUzFba40UJMhuu8qZmvN/6WWc=\";";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ switch (request.queryString) {
+ case "zeromaxagevalid":
+ response.setHeader("Public-Key-Pins", "max-age=0;" + VALIDPIN +
+ INVALIDPIN2 + "includeSubdomains");
+ break;
+ case "valid":
+ default:
+ response.setHeader("Public-Key-Pins", "max-age=50000;" + VALIDPIN +
+ INVALIDPIN2 + "includeSubdomains");
+ }
+
+ response.write("Hello world!" + request.host);
+}
diff --git a/browser/base/content/test/general/print_postdata.sjs b/browser/base/content/test/general/print_postdata.sjs
new file mode 100644
index 000000000..4175a2480
--- /dev/null
+++ b/browser/base/content/test/general/print_postdata.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/base/content/test/general/refresh_header.sjs b/browser/base/content/test/general/refresh_header.sjs
new file mode 100644
index 000000000..327372f9b
--- /dev/null
+++ b/browser/base/content/test/general/refresh_header.sjs
@@ -0,0 +1,24 @@
+/**
+ * Will cause an auto-refresh to the URL provided in the query string
+ * after some delay using the refresh HTTP header.
+ *
+ * Expects the query string to be in the format:
+ *
+ * ?p=[URL of the page to redirect to]&d=[delay]
+ *
+ * Example:
+ *
+ * ?p=http%3A%2F%2Fexample.org%2Fbrowser%2Fbrowser%2Fbase%2Fcontent%2Ftest%2Fgeneral%2Frefresh_meta.sjs&d=200
+ */
+function handleRequest(request, response) {
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let page = query.get("p");
+ let delay = query.get("d");
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "200", "Found");
+ response.setHeader("refresh", `${delay}; url=${page}`);
+ response.write("OK");
+} \ No newline at end of file
diff --git a/browser/base/content/test/general/refresh_meta.sjs b/browser/base/content/test/general/refresh_meta.sjs
new file mode 100644
index 000000000..648fac1a3
--- /dev/null
+++ b/browser/base/content/test/general/refresh_meta.sjs
@@ -0,0 +1,36 @@
+/**
+ * Will cause an auto-refresh to the URL provided in the query string
+ * after some delay using a <meta> tag.
+ *
+ * Expects the query string to be in the format:
+ *
+ * ?p=[URL of the page to redirect to]&d=[delay]
+ *
+ * Example:
+ *
+ * ?p=http%3A%2F%2Fexample.org%2Fbrowser%2Fbrowser%2Fbase%2Fcontent%2Ftest%2Fgeneral%2Frefresh_meta.sjs&d=200
+ */
+function handleRequest(request, response) {
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let page = query.get("p");
+ let delay = query.get("d");
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <META http-equiv='refresh' content='${delay}; url=${page}'>
+ <title>Gonna refresh you, folks.</title>
+ </head>
+ <body>
+ <h1>Wait for it...</h1>
+ </body>
+ </html>`;
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "200", "Found");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(html);
+} \ No newline at end of file
diff --git a/browser/base/content/test/general/searchSuggestionEngine.sjs b/browser/base/content/test/general/searchSuggestionEngine.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/base/content/test/general/searchSuggestionEngine.xml b/browser/base/content/test/general/searchSuggestionEngine.xml
new file mode 100644
index 000000000..3d1f294f5
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/general/searchSuggestionEngine2.xml b/browser/base/content/test/general/searchSuggestionEngine2.xml
new file mode 100644
index 000000000..05644649a
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine2.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine2&amp;terms={searchTerms}" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/general/ssl_error_reports.sjs b/browser/base/content/test/general/ssl_error_reports.sjs
new file mode 100644
index 000000000..e2e5bafc0
--- /dev/null
+++ b/browser/base/content/test/general/ssl_error_reports.sjs
@@ -0,0 +1,91 @@
+const EXPECTED_CHAIN = [
+ "MIIDCjCCAfKgAwIBAgIENUiGYDANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQxMDAxMjExNDE5WhcNMjQxMDAxMjExNDE5WjAxMS8wLQYDVQQDEyZpbmNsdWRlLXN1YmRvbWFpbnMucGlubmluZy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxYrge8C4eVfTb6/lJ4k/+/4J6wlnWpp5Szxy1MHhsLB+LJh/HRHqkO/tsigT204kTeU3dxuAfQHz0g+Td8dr6KICLLNVFUPw+XjhBV4AtxV8wcprs6EmdBhJgAjkFB4M76BL7/Ow0NfH012WNESn8TTbsp3isgkmrXjTZhWR33vIL1eDNimykp/Os/+JO+x9KVfdCtDCrPwO9Yusial5JiaW7qemRtVuUDL87NSJ7xokPEOSc9luv/fBamZ3rgqf3K6epqg+0o3nNCCcNFnfLW52G0t69+dIjr39WISHnqqZj3Sb7JPU6OmxTd13ByoLkoM3ZUQ2Lpas+RJvQyGXkCAwEAAaM1MDMwMQYDVR0RBCowKIImaW5jbHVkZS1zdWJkb21haW5zLnBpbm5pbmcuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAAmzXfeoOS59FkNABRonFPRyFl7BoGpVJENUteFfTa2pdAhGYdo19Y4uILTTj+vtDAa5yryb5Uvd+YuJnExosbMMkzCrmZ9+VJCJdqUTb+idwk9/sgPl2gtGeRmefB0hXSUFHc/p1CDufSpYOmj9NCUZD2JEsybgJQNulkfAsVnS3lzDcxAwcO+RC/1uJDSiUtcBpWS4FW58liuDYE7PD67kLJHZPVUV2WCMuIl4VM2tKPtvShz1JkZ5UytOLs6jPfviNAk/ftXczaE2/RJgM2MnDX9nGzOxG6ONcVNCljL8avhFBCosutE6i5LYSZR6V14YY/xOn15WDSuWdnIsJCo=",
+ "MIIC2jCCAcKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQwOTI1MjEyMTU0WhcNMjQwOTI1MjEyMTU0WjAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBT+BwAhO52IWgSIdZZifU9LHOs3IR/+8DCC0WP5d/OuyKlZ6Rqd0tsd3i7durhQyjHSbLf2lJStcnFjcVEbEnNI76RuvlN8xLLn5eV+2Ayr4cZYKztudwRmw+DV/iYAiMSy0hs7m3ssfX7qpoi1aNRjUanwU0VTCPQhF1bEKAC2du+C5Z8e92zN5t87w7bYr7lt+m8197XliXEu+0s9RgnGwGaZ296BIRz6NOoJYTa43n06LU1I1+Z4d6lPdzUFrSR0GBaMhUSurUBtOin3yWiMhg1VHX/KwqGc4als5GyCVXy8HGrA/0zQPOhetxrlhEVAdK/xBt7CZvByj1Rcc7AgMBAAGjEzARMA8GA1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAJq/hogSRqzPWTwX4wTn/DVSNdWwFLv53qep9YrSMJ8ZsfbfK9Es4VP4dBLRQAVMJ0Z5mW1I6d/n0KayTanuUBvemYdxPi/qQNSs8UJcllqdhqWzmzAg6a0LxrMnEeKzPBPD6q8PwQ7tYP+B4sBN9tnnsnyPgti9ZiNZn5FwXZliHXseQ7FE9/SqHlLw5LXW3YtKjuti6RmuV6fq3j+D4oeC5vb1mKgIyoTqGN6ze57v8RHi+pQ8Q+kmoUn/L3Z2YmFe4SKN/4WoyXr8TdejpThGOCGCAd3565s5gOx5QfSQX11P8NZKO8hcN0tme3VzmGpHK0Z/6MTmdpNaTwQ6odk="
+ ];
+
+const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = -16384;
+
+function parseReport(request) {
+ // read the report from the request
+ let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
+ inputStream.init(request.bodyInputStream, 0x01, 0004, 0);
+
+ let body = "";
+ if (inputStream) {
+ while (inputStream.available()) {
+ body = body + inputStream.read(inputStream.available());
+ }
+ }
+ // parse the report
+ return JSON.parse(body);
+}
+
+function handleRequest(request, response) {
+ let report = {};
+ let certChain = [];
+
+ switch (request.queryString) {
+ case "succeed":
+ report = parseReport(request);
+ certChain = report.failedCertChain;
+
+ // ensure the cert chain is what we expect
+ for (idx in certChain) {
+ if (certChain[idx] !== EXPECTED_CHAIN[idx]) {
+ // if the chain differs, send an error response to cause test
+ // failure
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected chain</html>");
+ return;
+ }
+ }
+
+ if (report.errorCode !== MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) {
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected error code</html>");
+ return;
+ }
+
+ // if all is as expected, send the 201 the client expects
+ response.setStatusLine("1.1", 201, "Created");
+ response.write("<html>OK</html>");
+ break;
+ case "nocert":
+ report = parseReport(request);
+ certChain = report.failedCertChain;
+
+ if (certChain && certChain.length > 0) {
+ // We're not expecting a chain; if there is one, send an error
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected chain</html>");
+ return;
+ }
+
+ // if all is as expected, send the 201 the client expects
+ response.setStatusLine("1.1", 201, "Created");
+ response.write("<html>OK</html>");
+ break;
+ case "badcert":
+ report = parseReport(request);
+ certChain = report.failedCertChain;
+
+ if (!certChain || certChain.length != 2) {
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected chain</html>");
+ return;
+ }
+
+ // if all is as expected, send the 201 the client expects
+ response.setStatusLine("1.1", 201, "Created");
+ response.write("<html>OK</html>");
+ break;
+ case "error":
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>server error</html>");
+ break;
+ default:
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>succeed, nocert or error expected (got " + request.queryString + ")</html>");
+ break;
+ }
+}
diff --git a/browser/base/content/test/general/subtst_contextmenu.html b/browser/base/content/test/general/subtst_contextmenu.html
new file mode 100644
index 000000000..1768f399f
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+Browser context menu subtest.
+
+<div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
+<input id="test-input"><br>
+<img id="test-image" src="ctxmenu-image.png">
+<canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
+<video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video>
+<video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
+<video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
+<video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
+ <source src="bogus.duh" type="video/durrrr;">
+</video>
+<iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-audio-in-iframe" src="audio.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe>
+<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
+<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
+<div id="test-contenteditable-spellcheck-false" contenteditable="true" spellcheck="false">test</div> <!-- No Check Spelling menu item -->
+<div id="test-dom-full-screen">DOM full screen FTW</div>
+<div contextmenu="myMenu">
+ <p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
+ <menu id="myMenu" type="context">
+ <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
+ <menuitem label="Disabled item" disabled></menuitem>
+ <menuitem> Item w/ textContent</menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox" checked></menuitem>
+ </menu>
+ <menu>
+ <menuitem type="radio" label="Radio1" checked></menuitem>
+ <menuitem type="radio" label="Radio2"></menuitem>
+ <menuitem type="radio" label="Radio3"></menuitem>
+ </menu>
+ <menu>
+ <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem>
+ <menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem>
+ </menu>
+ <menu label="Submenu">
+ <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox"></menuitem>
+ </menu>
+ </menu>
+ <menu hidden>
+ <menuitem label="Bogus item"></menuitem>
+ </menu>
+ <menu>
+ </menu>
+ <menuitem label="Hidden item" hidden></menuitem>
+ <menuitem></menuitem>
+ </menu>
+</div>
+<div id="test-select-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<div id="test-select-text-link">http://mozilla.com</div>
+<a id="test-image-link" href="#"><img src="ctxmenu-image.png"></a>
+<input id="test-select-input-text" type="text" value="input">
+<input id="test-select-input-text-type-password" type="password" value="password">
+<embed id="test-plugin" style="width: 200px; height: 200px;" type="application/x-test"></embed>
+<img id="test-longdesc" src="ctxmenu-image.png" longdesc="http://www.mozilla.org"></embed>
+<iframe id="test-srcdoc" width="98" height="98" srcdoc="Hello World" style="border: 1px solid black"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/general/subtst_contextmenu_input.html b/browser/base/content/test/general/subtst_contextmenu_input.html
new file mode 100644
index 000000000..c5be977ea
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu_input.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+ Browser context menu subtest.
+ <input id="input_text">
+ <input id="input_spellcheck_no_value">
+ <input id="input_spellcheck_incorrect" spellcheck="true" value="prodkjfgigrty">
+ <input id="input_spellcheck_correct" spellcheck="true" value="foo">
+ <input id="input_disabled" disabled="true">
+ <input id="input_password">
+ <input id="input_email" type="email">
+ <input id="input_tel" type="tel">
+ <input id="input_url" type="url">
+ <input id="input_number" type="number">
+ <input id="input_date" type="date">
+ <input id="input_time" type="time">
+ <input id="input_color" type="color">
+ <input id="input_range" type="range">
+ <input id="input_search" type="search">
+ <input id="input_datetime" type="datetime">
+ <input id="input_month" type="month">
+ <input id="input_week" type="week">
+ <input id="input_datetime-local" type="datetime-local">
+ <input id="input_readonly" readonly="true">
+</body>
+</html>
diff --git a/browser/base/content/test/general/subtst_contextmenu_xul.xul b/browser/base/content/test/general/subtst_contextmenu_xul.xul
new file mode 100644
index 000000000..5a2ab42e8
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu_xul.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <label id="test-xul-text-link-label" class="text-link" value="XUL text-link label" href="https://www.mozilla.com"/>
+</page>
diff --git a/browser/base/content/test/general/svg_image.html b/browser/base/content/test/general/svg_image.html
new file mode 100644
index 000000000..7ab17c33a
--- /dev/null
+++ b/browser/base/content/test/general/svg_image.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for page info svg images</title>
+ </head>
+ <body>
+ <svg width="20" height="20">
+ <image xlink:href="title_test.svg" width="20" height="20">
+ </svg>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/test-mixedcontent-securityerrors.html b/browser/base/content/test/general/test-mixedcontent-securityerrors.html
new file mode 100644
index 000000000..cb8cfdaaf
--- /dev/null
+++ b/browser/base/content/test/general/test-mixedcontent-securityerrors.html
@@ -0,0 +1,21 @@
+<!--
+ Bug 875456 - Log mixed content messages from the Mixed Content Blocker to the
+ Security Pane in the Web Console
+-->
+
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <title>Mixed Content test - http on https</title>
+ <script src="testscript.js"></script>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <iframe src="http://example.com"></iframe>
+ <img src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/test_bug364677.html b/browser/base/content/test/general/test_bug364677.html
new file mode 100644
index 000000000..67b9729d1
--- /dev/null
+++ b/browser/base/content/test/general/test_bug364677.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364677
+-->
+<head>
+ <title>Test for Bug 364677</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364677">Mozilla Bug 364677</a>
+<p id="display"><iframe id="testFrame" src="bug364677-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 364677 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "feedHandler",
+ "Feed served as text/xml without a channel/link should have been sniffed");
+});
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/general/test_bug395533.html b/browser/base/content/test/general/test_bug395533.html
new file mode 100644
index 000000000..ad6209047
--- /dev/null
+++ b/browser/base/content/test/general/test_bug395533.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=395533
+-->
+<head>
+ <title>Test for Bug 395533</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=395533">Mozilla Bug 395533</a>
+<p id="display"><iframe id="testFrame" src="bug395533-data.txt"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 395533 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Need privs because the feed seems to have an about:feeds principal or some
+ // such. It's not same-origin with us in any case.
+ is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "",
+ "Text got sniffed as a feed?");
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/general/test_bug435035.html b/browser/base/content/test/general/test_bug435035.html
new file mode 100644
index 000000000..a6624db15
--- /dev/null
+++ b/browser/base/content/test/general/test_bug435035.html
@@ -0,0 +1 @@
+<img src="http://example.com/browser/browser/base/content/test/general/moz.png">
diff --git a/browser/base/content/test/general/test_bug462673.html b/browser/base/content/test/general/test_bug462673.html
new file mode 100644
index 000000000..d864990e4
--- /dev/null
+++ b/browser/base/content/test/general/test_bug462673.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+var w;
+function openIt() {
+ w = window.open("", "window2");
+}
+function closeIt() {
+ if (w) {
+ w.close();
+ w = null;
+ }
+}
+</script>
+</head>
+<body onload="openIt();" onunload="closeIt();">
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_bug628179.html b/browser/base/content/test/general/test_bug628179.html
new file mode 100644
index 000000000..d35e17a7c
--- /dev/null
+++ b/browser/base/content/test/general/test_bug628179.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for closing the Find bar in subdocuments</title>
+ </head>
+ <body>
+ <iframe id=iframe src="http://example.com/" width=320 height=240></iframe>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/general/test_bug839103.html b/browser/base/content/test/general/test_bug839103.html
new file mode 100644
index 000000000..3639d4bda
--- /dev/null
+++ b/browser/base/content/test/general/test_bug839103.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Document for Bug 839103</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style></style>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_bug959531.html b/browser/base/content/test/general/test_bug959531.html
new file mode 100644
index 000000000..e749b198a
--- /dev/null
+++ b/browser/base/content/test/general/test_bug959531.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for content page with settings button</title>
+ </head>
+ <body>
+ <button name="settings" id="settings">Settings</button>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/test_mcb_double_redirect_image.html b/browser/base/content/test/general/test_mcb_double_redirect_image.html
new file mode 100644
index 000000000..1b54774ec
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_double_redirect_image.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 7-9 for Bug 1082837 - See file browser_mcb_redirect.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1082837
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1082837</title>
+ <script>
+ function image_loaded() {
+ document.getElementById("mctestdiv").innerHTML = "image loaded";
+ }
+ function image_blocked() {
+ document.getElementById("mctestdiv").innerHTML = "image blocked";
+ }
+ </script>
+</head>
+<body>
+ <div id="mctestdiv"></div>
+ <img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_http_sjs" onload="image_loaded()" onerror="image_blocked()" ></image>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_mcb_redirect.html b/browser/base/content/test/general/test_mcb_redirect.html
new file mode 100644
index 000000000..88af791a3
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 418354 - See file browser_mcb_redirect.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=418354
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Bug 418354</title>
+</head>
+<body>
+ <div id="mctestdiv">script blocked</div>
+ <script src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?script" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_mcb_redirect.js b/browser/base/content/test/general/test_mcb_redirect.js
new file mode 100644
index 000000000..48538c940
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "script executed";
diff --git a/browser/base/content/test/general/test_mcb_redirect.sjs b/browser/base/content/test/general/test_mcb_redirect.sjs
new file mode 100644
index 000000000..9a1811dfa
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect.sjs
@@ -0,0 +1,22 @@
+function handleRequest(request, response) {
+ var page = "<!DOCTYPE html><html><body>bug 418354 and bug 1082837</body></html>";
+
+ if (request.queryString === "script") {
+ var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.js";
+ response.setHeader("Cache-Control", "no-cache", false);
+ } else if (request.queryString === "image_http") {
+ var redirect = "http://example.com/tests/image/test/mochitest/blue.png";
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ } else if (request.queryString === "image_redirect_http_sjs") {
+ var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_https";
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ } else if (request.queryString === "image_redirect_https") {
+ var redirect = "https://example.com/tests/image/test/mochitest/blue.png";
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ }
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "302", "Found");
+ response.setHeader("Location", redirect, false);
+ response.write(page);
+}
diff --git a/browser/base/content/test/general/test_mcb_redirect_image.html b/browser/base/content/test/general/test_mcb_redirect_image.html
new file mode 100644
index 000000000..c70cd8987
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect_image.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3-6 for Bug 1082837 - See file browser_mcb_redirect.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1082837
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1082837</title>
+ <script>
+ function image_loaded() {
+ document.getElementById("mctestdiv").innerHTML = "image loaded";
+ }
+ function image_blocked() {
+ document.getElementById("mctestdiv").innerHTML = "image blocked";
+ }
+ </script>
+</head>
+<body>
+ <div id="mctestdiv"></div>
+ <img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_http" onload="image_loaded()" onerror="image_blocked()" ></image>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font.css b/browser/base/content/test/general/test_no_mcb_on_http_site_font.css
new file mode 100644
index 000000000..68a6954cc
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.css
@@ -0,0 +1,10 @@
+@font-face {
+ font-family: testFont;
+ src: url(http://example.com/browser/devtools/client/fontinspector/test/browser_font.woff);
+}
+body {
+ font-family: Arial;
+}
+div {
+ font-family: testFont;
+}
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font.html b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html
new file mode 100644
index 000000000..28a9cb2c0
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=909920
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 909920</title>
+ <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font.css" />
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+ function checkLoadStates() {
+ var ui = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .securityUI;
+
+ var loadedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+ is(loadedMixedActive, false, "OK: Should not load mixed active content!");
+
+ var blockedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+ is(blockedMixedActive, false, "OK: Should not block mixed active content!");
+
+ var loadedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
+
+ var blockedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+ is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
+
+ var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http font";
+ document.getElementById("testDiv").innerHTML = newValue;
+ }
+</script>
+</head>
+<body onload="checkLoadStates()">
+ <div class="testDiv" id="testDiv">
+ Testing MCB does not trigger warning/error for an http page with https css that includes http font
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css
new file mode 100644
index 000000000..f73b573b4
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css
@@ -0,0 +1 @@
+@import url(http://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font.css);
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html
new file mode 100644
index 000000000..2b3164902
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=909920
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 909920</title>
+ <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css" />
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+ function checkLoadStates() {
+ var ui = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .securityUI;
+
+ var loadedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+ is(loadedMixedActive, false, "OK: Should not load mixed active content!");
+
+ var blockedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+ is(blockedMixedActive, false, "OK: Should not block mixed active content!");
+
+ var loadedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
+
+ var blockedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+ is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
+
+ var newValue = "Verifying MCB does not trigger warning/error for an http page ";
+ newValue += "with https css that imports another http css which includes http font";
+ document.getElementById("testDiv").innerHTML = newValue;
+ }
+</script>
+</head>
+<body onload="checkLoadStates()">
+ <div class="testDiv" id="testDiv">
+ Testing MCB does not trigger warning/error for an http page with https css that imports another http css which includes http font
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_img.css b/browser/base/content/test/general/test_no_mcb_on_http_site_img.css
new file mode 100644
index 000000000..d045e21ba
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.css
@@ -0,0 +1,3 @@
+#testDiv {
+ background: url(http://example.com/tests/image/test/mochitest/blue.png)
+}
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_img.html b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html
new file mode 100644
index 000000000..741573260
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=909920
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 909920</title>
+ <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_img.css" />
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+ function checkLoadStates() {
+ var ui = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .securityUI;
+
+ var loadedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+ is(loadedMixedActive, false, "OK: Should not load mixed active content!");
+
+ var blockedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+ is(blockedMixedActive, false, "OK: Should not block mixed active content!");
+
+ var loadedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
+
+ var blockedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+ is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
+
+ var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http image";
+ document.getElementById("testDiv").innerHTML = newValue;
+ }
+</script>
+</head>
+<body onload="checkLoadStates()">
+ <div class="testDiv" id="testDiv">
+ Testing MCB does not trigger warning/error for an http page with https css that includes http image
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_offlineNotification.html b/browser/base/content/test/general/test_offlineNotification.html
new file mode 100644
index 000000000..4f78184b4
--- /dev/null
+++ b/browser/base/content/test/general/test_offlineNotification.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462856
+-->
+<head>
+ <title>Test offline app notification</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="offlineByDefault.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+<!-- Load the test frame twice from the same domain,
+ to make sure we get notifications for both -->
+<iframe name="testFrame" src="offlineChild.html"></iframe>
+<iframe name="testFrame2" src="offlineChild2.html"></iframe>
+<!-- Load from another domain to make sure we get a second allow/deny
+ notification -->
+<iframe name="testFrame3" src="http://example.com/tests/browser/base/content/test/general/offlineChild.html"></iframe>
+
+<iframe id="eventsTestFrame" src="offlineEvent.html"></iframe>
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+const Cc = SpecialPowers.Cc;
+
+var numFinished = 0;
+
+window.addEventListener("message", function(event) {
+ is(event.data, "success", "Child was successfully cached.");
+
+ if (++numFinished == 3) {
+ // Clean up after ourself
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(SpecialPowers.Ci.nsIPermissionManager);
+ var ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var uri1 = ioService.newURI(frames.testFrame.location, null, null);
+ var uri2 = ioService.newURI(frames.testFrame3.location, null, null);
+
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
+ var principal1 = ssm.createCodebasePrincipal(uri1, {});
+ var principal2 = ssm.createCodebasePrincipal(uri2, {});
+
+ pm.removeFromPrincipal(principal1, "offline-app");
+ pm.removeFromPrincipal(principal2, "offline-app");
+
+ offlineByDefault.reset();
+
+ SimpleTest.finish();
+ }
+ }, false);
+
+var count = 0;
+var expectedEvent = "";
+function eventHandler(evt) {
+ ++count;
+ is(evt.type, expectedEvent, "Wrong event!");
+}
+
+function testEventHandling() {
+ var events = [ "checking",
+ "error",
+ "noupdate",
+ "downloading",
+ "progress",
+ "updateready",
+ "cached",
+ "obsolete"];
+ var w = document.getElementById("eventsTestFrame").contentWindow;
+ var e;
+ for (var i = 0; i < events.length; ++i) {
+ count = 0;
+ expectedEvent = events[i];
+ e = w.document.createEvent("event");
+ e.initEvent(expectedEvent, true, true);
+ w.applicationCache["on" + expectedEvent] = eventHandler;
+ w.applicationCache.addEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 2, "Wrong number events!");
+ w.applicationCache["on" + expectedEvent] = null;
+ w.applicationCache.removeEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 2, "Wrong number events!");
+ }
+
+ // Test some random event.
+ count = 0;
+ expectedEvent = "foo";
+ e = w.document.createEvent("event");
+ e.initEvent(expectedEvent, true, true);
+ w.applicationCache.addEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 1, "Wrong number events!");
+ w.applicationCache.removeEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 1, "Wrong number events!");
+}
+
+function loaded() {
+ testEventHandling();
+
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates, which will eventually lead to getting messages from
+ // the children.
+ var wm = SpecialPowers.Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(SpecialPowers.Ci.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ var panel = win.PopupNotifications.panel;
+ is(panel.childElementCount, 2, "2 notifications being displayed");
+ panel.firstElementChild.button.click();
+
+ // should have dismissed one of the notifications.
+ is(panel.childElementCount, 1, "1 notification now being displayed");
+ panel.firstElementChild.button.click();
+}
+
+SimpleTest.waitForFocus(loaded);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_offline_gzip.html b/browser/base/content/test/general/test_offline_gzip.html
new file mode 100644
index 000000000..a18d6604e
--- /dev/null
+++ b/browser/base/content/test/general/test_offline_gzip.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=501422
+
+When content which was transported over the network with
+Content-Type: gzip is added to the offline
+cache, it can be fetched from the cache successfully.
+-->
+<head>
+ <title>Test gzipped offline resources</title>
+ <meta charset="utf-8">
+</head>
+<body>
+<p id="display">
+<iframe name="testFrame" src="gZipOfflineChild.html"></iframe>
+
+<div id="content" style="display: none">
+</div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_process_flags_chrome.html b/browser/base/content/test/general/test_process_flags_chrome.html
new file mode 100644
index 000000000..adcbf0340
--- /dev/null
+++ b/browser/base/content/test/general/test_process_flags_chrome.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+<p>chrome: test page</p>
+<p><a href="chrome://mochitests/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">chrome</a></p>
+<p><a href="chrome://mochitests-any/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">canremote</a></p>
+<p><a href="chrome://mochitests-content/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">mustremote</a></p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_remoteTroubleshoot.html b/browser/base/content/test/general/test_remoteTroubleshoot.html
new file mode 100644
index 000000000..7ba1c5268
--- /dev/null
+++ b/browser/base/content/test/general/test_remoteTroubleshoot.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+// This test is run multiple times, once with only strings allowed through the
+// WebChannel, and once with objects allowed. This function allows us to handle
+// both cases without too much pain.
+function makeDetails(object) {
+ if (window.location.search.indexOf("object") >= 0) {
+ return object;
+ }
+ return JSON.stringify(object)
+}
+// Add a listener for responses to our remote requests.
+window.addEventListener("WebChannelMessageToContent", function (event) {
+ if (event.detail.id == "remote-troubleshooting") {
+ // Send what we got back to the test.
+ var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: makeDetails({
+ id: "test-remote-troubleshooting-backchannel",
+ message: {
+ message: event.detail.message,
+ },
+ }),
+ });
+ window.dispatchEvent(backEvent);
+ // and stick it in our DOM just for good measure/diagnostics.
+ document.getElementById("troubleshooting").textContent =
+ JSON.stringify(event.detail.message, null, 2);
+ }
+});
+
+// Make a request for the troubleshooting data as we load.
+window.onload = function() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: makeDetails({
+ id: "remote-troubleshooting",
+ message: {
+ command: "request",
+ },
+ }),
+ });
+ window.dispatchEvent(event);
+}
+</script>
+
+<body>
+ <pre id="troubleshooting"/>
+</body>
+
+</html>
diff --git a/browser/base/content/test/general/title_test.svg b/browser/base/content/test/general/title_test.svg
new file mode 100644
index 000000000..7638fd5cc
--- /dev/null
+++ b/browser/base/content/test/general/title_test.svg
@@ -0,0 +1,59 @@
+<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title>This is a root SVG element's title</title>
+ <foreignObject>
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <svg xmlns="http://www.w3.org/2000/svg" id="svg1">
+ <title>This is a non-root SVG element title</title>
+ </svg>
+ </body>
+ </html>
+ </foreignObject>
+ <text id="text1" x="10px" y="32px" font-size="24px">
+ This contains only &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" xlink: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" xlink: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" xlink: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" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="256px" font-size="24px">
+ This link contains xlink:title attr.
+ </text>
+ </a>
+ <text id="text5" x="10px" y="160px" font-size="24px"
+ xlink:title="This is an xlink:title attribute but it isn't on a link" >
+ This contains nothing.
+ </text>
+</svg>
diff --git a/browser/base/content/test/general/trackingPage.html b/browser/base/content/test/general/trackingPage.html
new file mode 100644
index 000000000..17f0e459e
--- /dev/null
+++ b/browser/base/content/test/general/trackingPage.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <iframe src="http://tracking.example.com/"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/unknownContentType_file.pif b/browser/base/content/test/general/unknownContentType_file.pif
new file mode 100644
index 000000000..9353d1312
--- /dev/null
+++ b/browser/base/content/test/general/unknownContentType_file.pif
@@ -0,0 +1 @@
+Dummy content for unknownContentType_dialog_layout_data.pif
diff --git a/browser/base/content/test/general/unknownContentType_file.pif^headers^ b/browser/base/content/test/general/unknownContentType_file.pif^headers^
new file mode 100644
index 000000000..09b22facc
--- /dev/null
+++ b/browser/base/content/test/general/unknownContentType_file.pif^headers^
@@ -0,0 +1 @@
+Content-Type: application/octet-stream
diff --git a/browser/base/content/test/general/video.ogg b/browser/base/content/test/general/video.ogg
new file mode 100644
index 000000000..ac7ece351
--- /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 000000000..467fb0ce1
--- /dev/null
+++ b/browser/base/content/test/general/web_video.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>Document with Web Video</title>
+ </head>
+ <body>
+ This document has some web video in it.
+ <br>
+ <video src="web_video1.ogv" id="video1"> </video>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/web_video1.ogv b/browser/base/content/test/general/web_video1.ogv
new file mode 100644
index 000000000..093158432
--- /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 000000000..4511e9255
--- /dev/null
+++ b/browser/base/content/test/general/web_video1.ogv^headers^
@@ -0,0 +1,3 @@
+Content-Disposition: filename="web-video1-expectedName.ogv"
+Content-Type: video/ogg
+
diff --git a/browser/base/content/test/general/zoom_test.html b/browser/base/content/test/general/zoom_test.html
new file mode 100644
index 000000000..bf80490ca
--- /dev/null
+++ b/browser/base/content/test/general/zoom_test.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416661
+-->
+ <head>
+ <title>Test for zoom setting</title>
+
+ </head>
+ <body>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=416661">Bug 416661</a>
+ <p>Site specific zoom settings should not apply to image documents.</p>
+ </body>
+</html>
diff --git a/browser/base/content/test/newtab/.eslintrc.js b/browser/base/content/test/newtab/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/newtab/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/newtab/browser.ini b/browser/base/content/test/newtab/browser.ini
new file mode 100644
index 000000000..2d14d208d
--- /dev/null
+++ b/browser/base/content/test/newtab/browser.ini
@@ -0,0 +1,55 @@
+[DEFAULT]
+skip-if = (os == 'linux') # Bug 1243103, Bug 1243398, etc.
+support-files =
+ head.js
+
+[browser_newtab_1188015.js]
+[browser_newtab_background_captures.js]
+[browser_newtab_block.js]
+[browser_newtab_bug721442.js]
+[browser_newtab_bug722273.js]
+skip-if = (os == "mac" && debug) # temporary skip-if due to increase in intermittent failures on Mac debug - bug 1119906
+[browser_newtab_bug723102.js]
+[browser_newtab_bug723121.js]
+[browser_newtab_bug725996.js]
+[browser_newtab_bug734043.js]
+[browser_newtab_bug735987.js]
+[browser_newtab_bug752841.js]
+[browser_newtab_bug765628.js]
+[browser_newtab_bug876313.js]
+[browser_newtab_bug991111.js]
+[browser_newtab_bug991210.js]
+[browser_newtab_bug998387.js]
+[browser_newtab_bug1145428.js]
+[browser_newtab_bug1178586.js]
+[browser_newtab_bug1194895.js]
+[browser_newtab_bug1271075.js]
+[browser_newtab_disable.js]
+[browser_newtab_drag_drop.js]
+[browser_newtab_drag_drop_ext.js]
+# temporary until determine why more intermittent on VM
+subsuite = clipboard
+[browser_newtab_drop_preview.js]
+[browser_newtab_enhanced.js]
+[browser_newtab_focus.js]
+[browser_newtab_perwindow_private_browsing.js]
+[browser_newtab_reportLinkAction.js]
+[browser_newtab_reflow_load.js]
+support-files =
+ content-reflows.js
+[browser_newtab_search.js]
+support-files =
+ searchEngineNoLogo.xml
+ searchEngineFavicon.xml
+ searchEngine1xLogo.xml
+ searchEngine2xLogo.xml
+ searchEngine1x2xLogo.xml
+ ../general/searchSuggestionEngine.xml
+ ../general/searchSuggestionEngine.sjs
+[browser_newtab_sponsored_icon_click.js]
+skip-if = true # Bug 1314619
+[browser_newtab_undo.js]
+# temporary until determine why more intermittent on VM
+subsuite = clipboard
+[browser_newtab_unpin.js]
+[browser_newtab_update.js]
diff --git a/browser/base/content/test/newtab/browser_newtab_1188015.js b/browser/base/content/test/newtab/browser_newtab_1188015.js
new file mode 100644
index 000000000..f19aae1b9
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_1188015.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "directory": [{
+ url: "http://example1.com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title1",
+ type: "affiliate",
+ titleBgColor: "green"
+ }]
+});
+
+add_task(function* () {
+ yield pushPrefs(["browser.newtab.preload", false]);
+
+ // Make the page have a directory link
+ yield setLinks([]);
+ yield* addNewTabPageTab();
+
+ let color = yield performOnCell(0, cell => {
+ return cell.node.querySelector(".newtab-title").style.backgroundColor;
+ });
+
+ is(color, "green", "title bg color is green");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_background_captures.js b/browser/base/content/test/newtab/browser_newtab_background_captures.js
new file mode 100644
index 000000000..5e838196e
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verifies that hidden, pre-loaded newtabs don't allow background captures, and
+ * when unhidden, do allow background captures.
+ */
+
+const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
+
+add_task(function* () {
+ let imports = {};
+ Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
+
+ // Disable captures.
+ yield pushPrefs([CAPTURE_PREF, false]);
+
+ // Make sure the thumbnail doesn't exist yet.
+ let url = "http://example.com/";
+ let path = imports.PageThumbsStorage.getFilePathForURL(url);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(path);
+ try {
+ file.remove(false);
+ }
+ catch (err) {}
+
+ // Add a top site.
+ yield setLinks("-1");
+
+ // We need a handle to a hidden, pre-loaded newtab so we can verify that it
+ // doesn't allow background captures. Ensure we have a preloaded browser.
+ gBrowser._createPreloadBrowser();
+
+ // Wait for the preloaded browser to load.
+ if (gBrowser._preloadedBrowser.contentDocument.readyState != "complete") {
+ yield BrowserTestUtils.waitForEvent(gBrowser._preloadedBrowser, "load", true);
+ }
+
+ // We're now ready to use the preloaded browser.
+ BrowserOpenTab();
+ let tab = gBrowser.selectedTab;
+
+ let thumbnailCreatedPromise = new Promise(resolve => {
+ // Showing the preloaded tab should trigger thumbnail capture.
+ Services.obs.addObserver(function onCreate(subj, topic, data) {
+ if (data != url)
+ return;
+ Services.obs.removeObserver(onCreate, "page-thumbnail:create");
+ ok(true, "thumbnail created after preloaded tab was shown");
+
+ resolve();
+ }, "page-thumbnail:create", false);
+ });
+
+ // Enable captures.
+ yield pushPrefs([CAPTURE_PREF, false]);
+
+ yield thumbnailCreatedPromise;
+
+ // Test finished!
+ gBrowser.removeTab(tab);
+ file.remove(false);
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_block.js b/browser/base/content/test/newtab/browser_newtab_block.js
new file mode 100644
index 000000000..70656462a
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+/*
+ * These tests make sure that blocking/removing sites from the grid works
+ * as expected. Pinned tabs should not be moved. Gaps will be re-filled
+ * if more sites are available.
+ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "suggested": [{
+ url: "http://suggested.com/",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ title: "title",
+ type: "affiliate",
+ adgroup_name: "test",
+ frecent_sites: ["example0.com"]
+ }]
+});
+
+add_task(function* () {
+ let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+ DirectoryLinksProvider.getFrecentSitesName = () => "";
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = (site) => false;
+
+ // we remove sites and expect the gaps to be filled as long as there still
+ // are some sites available
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield blockCell(4);
+ yield* checkGrid("0,1,2,3,5,6,7,8,9");
+
+ yield blockCell(4);
+ yield* checkGrid("0,1,2,3,6,7,8,9,");
+
+ yield blockCell(4);
+ yield* checkGrid("0,1,2,3,7,8,9,,");
+
+ // we removed a pinned site
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ yield blockCell(1);
+ yield* checkGrid("0,2,3,4,5,6,7,8,");
+
+ // we remove the last site on the grid (which is pinned) and expect the gap
+ // to be re-filled and the new site to be unpinned
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield blockCell(8);
+ yield* checkGrid("0,1,2,3,4,5,6,7,9");
+
+ // we remove the first site on the grid with the last one pinned. all cells
+ // but the last one should shift to the left and a new site fades in
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield blockCell(0);
+ yield* checkGrid("1,2,3,4,5,6,7,9,8p");
+
+ // Test that blocking the targeted site also removes its associated suggested tile
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced on
+ yield* addNewTabPageTab();
+
+ yield* checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
+
+ yield blockCell(1);
+ yield* addNewTabPageTab();
+ yield* checkGrid("1,2,3,4,5,6,7,8,9");
+ DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1145428.js b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
new file mode 100644
index 000000000..72fe70212
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that pinning suggested tile results in:
+ * - making suggested tile a history tile and replacing enhancedImageURI with imageURI
+ * - upond end of campaign, replaces landing url with baseDomain and switches
+ * background image to thumbnail
+ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "suggested": [{
+ url: "http://example.com/landing/page.html",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title",
+ type: "affiliate",
+ adgroup_name: "example",
+ frecent_sites: ["example0.com"],
+ }]
+});
+
+add_task(function* () {
+ let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+ DirectoryLinksProvider.getFrecentSitesName = () => "";
+
+ function getData(cellNum) {
+ return performOnCell(cellNum, cell => {
+ if (!cell.site)
+ return null;
+ let siteNode = cell.site.node;
+ return {
+ type: siteNode.getAttribute("type"),
+ thumbnail: siteNode.querySelector(".newtab-thumbnail.thumbnail").style.backgroundImage,
+ enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+ title: siteNode.querySelector(".newtab-title").textContent,
+ suggested: siteNode.getAttribute("suggested"),
+ url: siteNode.querySelector(".newtab-link").getAttribute("href"),
+ };
+ });
+ }
+
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ // load another newtab since the first may not get suggested tile
+ yield* addNewTabPageTab();
+ yield* checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9");
+ // evaluate suggested tile
+ let tileData = yield getData(0);
+ is(tileData.type, "affiliate", "unpinned type");
+ is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
+ is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
+ is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists");
+ is(tileData.url, "http://example.com/landing/page.html", "unpinned landing page");
+
+ // suggested tile should not be pinned
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), false, "suggested tile is not pinned");
+
+ // pin suggested tile
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+
+ // tile should be pinned and turned into history tile
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), true, "suggested tile is pinned");
+ tileData = yield getData(0);
+ is(tileData.type, "history", "pinned type");
+ is(tileData.suggested, null, "no suggested attribute");
+ is(tileData.url, "http://example.com/landing/page.html", "original landing page");
+
+ // set pinned tile endTime into past and reload the page
+ NewTabUtils.pinnedLinks._links[0].endTime = Date.now() - 1000;
+ yield* addNewTabPageTab();
+
+ // check that url is reset to base domain and thumbnail points to moz-page-thumb service
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/"}), true, "baseDomain url is pinned");
+ tileData = yield getData(0);
+ is(tileData.type, "history", "type is history");
+ is(tileData.title, "example.com", "title changed to baseDomain");
+ is(tileData.thumbnail.indexOf("moz-page-thumb") != -1, true, "thumbnail contains moz-page-thumb");
+ is(tileData.enhanced, "", "no enhanced image");
+ is(tileData.url, "http://example.com/", "url points to baseDomian");
+
+ DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1178586.js b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
new file mode 100644
index 000000000..84d5cb577
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that pinned suggested tile turns into history tile
+ * and remains a history tile after a user clicks on it
+ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "suggested": [{
+ url: "http://example.com/hardlanding/page.html",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title",
+ type: "affiliate",
+ adgroup_name: "example",
+ frecent_sites: ["example0.com"],
+ }]
+});
+
+add_task(function* () {
+ let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+ DirectoryLinksProvider.getFrecentSitesName = () => "";
+
+ function getData(cellNum) {
+ return performOnCell(cellNum, cell => {
+ if (!cell.site)
+ return null;
+ let siteNode = cell.site.node;
+ return {
+ type: siteNode.getAttribute("type"),
+ thumbnail: siteNode.querySelector(".newtab-thumbnail.thumbnail").style.backgroundImage,
+ enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+ title: siteNode.querySelector(".newtab-title").textContent,
+ suggested: siteNode.getAttribute("suggested"),
+ url: siteNode.querySelector(".newtab-link").getAttribute("href"),
+ };
+ });
+ }
+
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ // load another newtab since the first may not get suggested tile
+ yield* addNewTabPageTab();
+ yield* checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9");
+ // evaluate suggested tile
+ let tileData = yield getData(0);
+ is(tileData.type, "affiliate", "unpinned type");
+ is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
+ is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
+ is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists");
+ is(tileData.url, "http://example.com/hardlanding/page.html", "unpinned landing page");
+
+ // suggested tile should not be pinned
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), false, "suggested tile is not pinned");
+
+ // pin suggested tile
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+
+ // tile should be pinned and turned into history tile
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), true, "suggested tile is pinned");
+ tileData = yield getData(0);
+ is(tileData.type, "history", "pinned type");
+ is(tileData.suggested, null, "no suggested attribute");
+ is(tileData.url, "http://example.com/hardlanding/page.html", "original landing page");
+
+ // click the pinned tile
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
+ // add new page twice to avoid using cached version
+ yield* addNewTabPageTab();
+ yield* addNewTabPageTab();
+
+ // check that type and suggested did not change
+ tileData = yield getData(0);
+ is(tileData.type, "history", "pinned type");
+ is(tileData.suggested, null, "no suggested attribute");
+
+ DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1194895.js b/browser/base/content/test/newtab/browser_newtab_bug1194895.js
new file mode 100644
index 000000000..c08b23185
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1194895.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PRELOAD_PREF = "browser.newtab.preload";
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
+
+function populateDirectoryTiles() {
+ let directoryTiles = [];
+ let i = 0;
+ while (i++ < 14) {
+ directoryTiles.push({
+ directoryId: i,
+ url: "http://example" + i + ".com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD",
+ title: "dirtitle" + i,
+ type: "affiliate"
+ });
+ }
+ return directoryTiles;
+}
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "directory": populateDirectoryTiles()
+});
+
+
+add_task(function* () {
+ requestLongerTimeout(4);
+ let origEnhanced = NewTabUtils.allPages.enhanced;
+ let origCompareLinks = NewTabUtils.links.compareLinks;
+ registerCleanupFunction(() => {
+ NewTabUtils.allPages.enhanced = origEnhanced;
+ NewTabUtils.links.compareLinks = origCompareLinks;
+ });
+
+ // turn off preload to ensure grid updates on every setLinks
+ yield pushPrefs([PRELOAD_PREF, false]);
+ // set newtab to have three columns only
+ yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
+ yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
+
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+
+ // Testing history tiles
+
+ // two rows of tiles should always fit on any screen
+ yield setLinks("0,1,2,3,4,5");
+ yield* addNewTabPageTab();
+
+ // should do not see scrollbar since tiles fit into visible space
+ yield* checkGrid("0,1,2,3,4,5");
+ let scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar");
+
+ // add enough tiles to cause extra two rows and observe scrollbar
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8,9");
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "document has scrollbar");
+
+ // pin the last tile to make it stay at the bottom of the newtab
+ yield pinCell(9);
+ // block first 6 tiles, which should not remove the scroll bar
+ // since the last tile is pinned in the nineth position
+ for (let i = 0; i < 6; i++) {
+ yield blockCell(0);
+ }
+ yield* addNewTabPageTab();
+ yield* checkGrid("6,7,8,,,,,,,9p");
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "document has scrollbar when tile is pinned to the last row");
+
+ // unpin the site: this will move tile up and make scrollbar disappear
+ yield unpinCell(9);
+ yield* addNewTabPageTab();
+ yield* checkGrid("6,7,8,9");
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar when bottom row tile is unpinned");
+
+ // reset everything to clean slate
+ NewTabUtils.restore();
+
+ // Testing directory tiles
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced on
+
+ // setup page with no history tiles to test directory only display
+ yield setLinks([]);
+ yield* addNewTabPageTab();
+ ok(!scrolling, "no scrollbar for directory tiles");
+
+ // introduce one history tile - it should occupy the last
+ // available slot at the bottom of newtab and cause scrollbar
+ yield setLinks("41");
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "adding low frecency history site causes scrollbar");
+
+ // set PREF_NEWTAB_ROWS to 4, that should clip off the history tile
+ // and remove scroll bar
+ yield pushPrefs([PREF_NEWTAB_ROWS, 4]);
+ yield* addNewTabPageTab();
+
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar if history tiles falls past max rows");
+
+ // restore max rows and watch scrollbar re-appear
+ yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "scrollbar is back when max rows allow for bottom history tile");
+
+ // block that history tile, and watch scrollbar disappear
+ yield blockCell(14);
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar after bottom history tiles is blocked");
+
+ // Test well-populated user history - newtab has highly-frecent history sites
+ // redefine compareLinks to always choose history tiles first
+ NewTabUtils.links.compareLinks = function (aLink1, aLink2) {
+ if (aLink1.type == aLink2.type) {
+ return aLink2.frecency - aLink1.frecency ||
+ aLink2.lastVisitDate - aLink1.lastVisitDate;
+ }
+ if (aLink2.type == "history") {
+ return 1;
+ }
+ return -1;
+ };
+
+ // add a row of history tiles, directory tiles will be clipped off, hence no scrollbar
+ yield setLinks("31,32,33");
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar when directory tiles follow history tiles");
+
+ // fill first four rows with history tiles and observer scrollbar
+ yield setLinks("30,31,32,33,34,35,36,37,38,39");
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "scrollbar appears when history tiles need extra row");
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1271075.js b/browser/base/content/test/newtab/browser_newtab_bug1271075.js
new file mode 100644
index 000000000..723b48fc6
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1271075.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add a few tabs.
+ let tabs = [];
+ function addTab(aURL, aReferrer) {
+ let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer});
+ tabs.push(tab);
+ return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ }
+
+ yield addTab("http://mochi.test:8888/#0");
+ yield addTab("http://mochi.test:8888/#1");
+ yield addTab("http://mochi.test:8888/#2");
+ yield addTab("http://mochi.test:8888/#3");
+
+ // Create a new tab page with a "www.example.com" tile and move it to the 2nd tab position.
+ yield setLinks("-1");
+ yield* addNewTabPageTab();
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+
+ // Send a middle-click and confirm that the clicked site opens immediately next to the new tab page.
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell",
+ {button: 1}, gBrowser.selectedBrowser);
+
+ yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserAtIndex(2));
+ is(gBrowser.getBrowserAtIndex(2).currentURI.spec, "http://example.com/",
+ "Middle click opens site in a new tab immediately to the right.");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug721442.js b/browser/base/content/test/newtab/browser_newtab_bug721442.js
new file mode 100644
index 000000000..99bd8d930
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks([
+ {url: "http://example7.com/", title: ""},
+ {url: "http://example8.com/", title: "title"},
+ {url: "http://example9.com/", title: "http://example9.com/"}
+ ]);
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("7p,8p,9p,0,1,2,3,4,5");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ function checkTooltip(aIndex, aExpected, aMessage) {
+ let cell = content.gGrid.cells[aIndex];
+
+ let link = cell.node.querySelector(".newtab-link");
+ Assert.equal(link.getAttribute("title"), aExpected, aMessage);
+ }
+
+ checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
+ checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
+ checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
+ });
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug722273.js b/browser/base/content/test/newtab/browser_newtab_bug722273.js
new file mode 100644
index 000000000..5cbfcd3ff
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NOW = Date.now() * 1000;
+const URL = "http://fake-site.com/";
+
+var tmp = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+var {Sanitizer} = tmp;
+
+add_task(function* () {
+ yield promiseSanitizeHistory();
+ yield promiseAddFakeVisits();
+ yield* addNewTabPageTab();
+
+ let cellUrl = yield performOnCell(0, cell => { return cell.site.url; });
+ is(cellUrl, URL, "first site is our fake site");
+
+ let updatedPromise = whenPagesUpdated();
+ yield promiseSanitizeHistory();
+ yield updatedPromise;
+
+ let isGone = yield performOnCell(0, cell => { return cell.site == null; });
+ ok(isGone, "fake site is gone");
+});
+
+function promiseAddFakeVisits() {
+ let visits = [];
+ for (let i = 59; i > 0; i--) {
+ visits.push({
+ visitDate: NOW - i * 60 * 1000000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ });
+ }
+ let place = {
+ uri: makeURI(URL),
+ title: "fake site",
+ visits: visits
+ };
+ return new Promise((resolve, reject) => {
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: () => reject(new Error("Couldn't add visit")),
+ handleResult: function () {},
+ handleCompletion: function () {
+ NewTabUtils.links.populateCache(function () {
+ NewTabUtils.allPages.update();
+ resolve();
+ }, true);
+ }
+ });
+ });
+}
+
+function promiseSanitizeHistory() {
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let prefs = gPrefService.getBranch(s.prefDomain);
+ prefs.setBoolPref("history", true);
+ prefs.setBoolPref("downloads", false);
+ prefs.setBoolPref("cache", false);
+ prefs.setBoolPref("cookies", false);
+ prefs.setBoolPref("formdata", false);
+ prefs.setBoolPref("offlineApps", false);
+ prefs.setBoolPref("passwords", false);
+ prefs.setBoolPref("sessions", false);
+ prefs.setBoolPref("siteSettings", false);
+
+ return s.sanitize();
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug723102.js b/browser/base/content/test/newtab/browser_newtab_bug723102.js
new file mode 100644
index 000000000..02282dc97
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // create a new tab page and hide it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ let firstTab = gBrowser.selectedTab;
+
+ yield* addNewTabPageTab();
+ yield BrowserTestUtils.removeTab(firstTab);
+
+ ok(NewTabUtils.allPages.enabled, "page is enabled");
+ NewTabUtils.allPages.enabled = false;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ Assert.ok(content.gGrid.node.hasAttribute("page-disabled"), "page is disabled");
+ });
+
+ NewTabUtils.allPages.enabled = true;
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug723121.js b/browser/base/content/test/newtab/browser_newtab_bug723121.js
new file mode 100644
index 000000000..82f45ebd5
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+ let grid = content.gGrid;
+ let cell = grid.cells[0];
+ let site = cell.site.node;
+ let link = site.querySelector(".newtab-link");
+
+ function checkGridLocked(aLocked, aMessage) {
+ Assert.equal(grid.node.hasAttribute("locked"), aLocked, aMessage);
+ }
+
+ function sendDragEvent(aEventType, aTarget) {
+ let dataTransfer = new content.DataTransfer(aEventType, false);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent(aEventType, true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+ aTarget.dispatchEvent(event);
+ }
+
+ checkGridLocked(false, "grid is unlocked");
+
+ sendDragEvent("dragstart", link);
+ checkGridLocked(true, "grid is now locked");
+
+ sendDragEvent("dragend", link);
+ checkGridLocked(false, "grid isn't locked anymore");
+
+ sendDragEvent("dragstart", cell.node);
+ checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+
+ sendDragEvent("dragstart", site);
+ checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+ });
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug725996.js b/browser/base/content/test/newtab/browser_newtab_bug725996.js
new file mode 100644
index 000000000..e0de809c8
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ function doDrop(data) {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { data: data }, function*(args) {
+ let dataTransfer = new content.DataTransfer("dragstart", false);
+ dataTransfer.mozSetDataAt("text/x-moz-url", args.data, 0);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[0].node;
+ target.dispatchEvent(event);
+ });
+ }
+
+ yield doDrop("http://example99.com/\nblank");
+ is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
+ "first cell is pinned and contains the dropped site");
+
+ yield whenPagesUpdated();
+ yield* checkGrid("99p,0,1,2,3,4,5,6,7");
+
+ yield doDrop("");
+ is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
+ "first cell is still pinned with the site we dropped before");
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug734043.js b/browser/base/content/test/newtab/browser_newtab_bug734043.js
new file mode 100644
index 000000000..02f765274
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.addEventListener("error", function () {
+ sendAsyncMessage("test:newtab-error", {});
+ });
+ });
+
+ let receivedError = false;
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("test:newtab-error", function onResponse(message) {
+ mm.removeMessageListener("test:newtab-error", onResponse);
+ ok(false, "Error event happened");
+ receivedError = true;
+ });
+
+ let pagesUpdatedPromise = whenPagesUpdated();
+
+ for (let i = 0; i < 3; i++) {
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block", {}, gBrowser.selectedBrowser);
+ }
+
+ yield pagesUpdatedPromise;
+
+ ok(!receivedError, "we got here without any errors");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug735987.js b/browser/base/content/test/newtab/browser_newtab_bug735987.js
new file mode 100644
index 000000000..2ae541c70
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield* simulateExternalDrop(1);
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield blockCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield* simulateExternalDrop(1);
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ // Simulate a restart and force the next about:newtab
+ // instance to read its data from the storage again.
+ NewTabUtils.blockedLinks.resetCache();
+
+ // Update all open pages, e.g. preloaded ones.
+ NewTabUtils.allPages.update();
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield blockCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug752841.js b/browser/base/content/test/newtab/browser_newtab_bug752841.js
new file mode 100644
index 000000000..e3faad13f
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug752841.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+
+function getCellsCount()
+{
+ return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ return content.gGrid.cells.length;
+ });
+}
+
+add_task(function* () {
+ let testValues = [
+ {row: 0, column: 0},
+ {row: -1, column: -1},
+ {row: -1, column: 0},
+ {row: 0, column: -1},
+ {row: 2, column: 4},
+ {row: 2, column: 5},
+ ];
+
+ // Expected length of grid
+ let expectedValues = [1, 1, 1, 1, 8, 10];
+
+ // Values before setting new pref values (15 is the default value -> 5 x 3)
+ let previousValues = [15, 1, 1, 1, 1, 8];
+
+ yield* addNewTabPageTab();
+ let existingTab = gBrowser.selectedTab;
+
+ for (let i = 0; i < expectedValues.length; i++) {
+ let existingTabGridLength = yield getCellsCount();
+ is(existingTabGridLength, previousValues[i],
+ "Grid length of existing page before update is correctly.");
+
+ yield pushPrefs([PREF_NEWTAB_ROWS, testValues[i].row]);
+ yield pushPrefs([PREF_NEWTAB_COLUMNS, testValues[i].column]);
+
+ existingTabGridLength = yield getCellsCount();
+ is(existingTabGridLength, expectedValues[i],
+ "Existing page grid is updated correctly.");
+
+ yield* addNewTabPageTab();
+ let newTab = gBrowser.selectedTab;
+ let newTabGridLength = yield getCellsCount();
+ is(newTabGridLength, expectedValues[i],
+ "New page grid is updated correctly.");
+
+ yield BrowserTestUtils.removeTab(newTab);
+ }
+
+ gBrowser.removeTab(existingTab);
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug765628.js b/browser/base/content/test/newtab/browser_newtab_bug765628.js
new file mode 100644
index 000000000..25afd32a9
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+ const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
+ const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
+
+ function sendDropEvent(aCellIndex, aDragData) {
+ let dataTransfer = new content.DataTransfer("dragstart", false);
+ dataTransfer.mozSetDataAt("text/x-moz-url", aDragData, 0);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[aCellIndex].node;
+ target.dispatchEvent(event);
+ }
+
+ sendDropEvent(0, BAD_DRAG_DATA);
+ sendDropEvent(1, GOOD_DRAG_DATA);
+ });
+
+ yield whenPagesUpdated();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug876313.js b/browser/base/content/test/newtab/browser_newtab_bug876313.js
new file mode 100644
index 000000000..1c0b0f501
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug876313.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * This test makes sure that the changes made by unpinning
+ * a site are actually written to NewTabUtils' storage.
+ */
+add_task(function* () {
+ // Second cell is pinned with page #99.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",99");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ // Unpin the second cell's site.
+ yield unpinCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // Clear the pinned cache to force NewTabUtils to read the pref again.
+ NewTabUtils.pinnedLinks.resetCache();
+ NewTabUtils.allPages.update();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug991111.js b/browser/base/content/test/newtab/browser_newtab_bug991111.js
new file mode 100644
index 000000000..37aa8213b
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // set max rows to 1, to avoid scroll events by clicking middle button
+ yield pushPrefs(["browser.newtabpage.rows", 1]);
+ yield setLinks("-1");
+ yield* addNewTabPageTab();
+ // we need a second newtab to honor max rows
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
+ let {site} = content.wrappedJSObject.gGrid.cells[args.index];
+
+ let origOnClick = site.onClick;
+ site.onClick = e => {
+ origOnClick.call(site, e);
+ sendAsyncMessage("test:clicked-on-cell", {});
+ };
+ });
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let messagePromise = new Promise(resolve => {
+ mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
+ mm.removeMessageListener("test:clicked-on-cell", onResponse);
+ resolve();
+ });
+ });
+
+ // Send a middle-click and make sure it happened
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell",
+ {button: 1}, gBrowser.selectedBrowser);
+ yield messagePromise;
+ ok(true, "middle click triggered click listener");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug991210.js b/browser/base/content/test/newtab/browser_newtab_bug991210.js
new file mode 100644
index 000000000..367c49f5c
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug991210.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // turn off preload to ensure that a newtab page loads
+ yield pushPrefs(["browser.newtab.preload", false]);
+
+ // add a test provider that waits for load
+ let afterLoadProvider = {
+ getLinks: function(callback) {
+ this.callback = callback;
+ },
+ addObserver: function() {},
+ };
+ NewTabUtils.links.addProvider(afterLoadProvider);
+
+ // wait until about:newtab loads before calling provider callback
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab");
+
+ afterLoadProvider.callback([]);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let {_cellHeight, _cellWidth, node} = content.gGrid;
+ Assert.notEqual(_cellHeight, null, "grid has a computed cell height");
+ Assert.notEqual(_cellWidth, null, "grid has a computed cell width");
+ let {height, maxHeight, maxWidth} = node.style;
+ Assert.notEqual(height, "", "grid has a computed grid height");
+ Assert.notEqual(maxHeight, "", "grid has a computed grid max-height");
+ Assert.notEqual(maxWidth, "", "grid has a computed grid max-width");
+ });
+
+ // restore original state
+ NewTabUtils.links.removeProvider(afterLoadProvider);
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug998387.js b/browser/base/content/test/newtab/browser_newtab_bug998387.js
new file mode 100644
index 000000000..30424c2e5
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug998387.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // set max rows to 1, to avoid scroll events by clicking middle button
+ yield pushPrefs(["browser.newtabpage.rows", 1]);
+ yield setLinks("0");
+ yield* addNewTabPageTab();
+ // we need a second newtab to honor max rows
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
+ let {site} = content.wrappedJSObject.gGrid.cells[args.index];
+
+ let origOnClick = site.onClick;
+ site.onClick = e => {
+ origOnClick.call(site, e);
+ sendAsyncMessage("test:clicked-on-cell", {});
+ };
+ });
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let messagePromise = new Promise(resolve => {
+ mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
+ mm.removeMessageListener("test:clicked-on-cell", onResponse);
+ resolve();
+ });
+ });
+
+ // Send a middle-click and make sure it happened
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block",
+ {button: 1}, gBrowser.selectedBrowser);
+
+ yield messagePromise;
+ ok(true, "middle click triggered click listener");
+
+ // Make sure the cell didn't actually get blocked
+ yield* checkGrid("0");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_disable.js b/browser/base/content/test/newtab/browser_newtab_disable.js
new file mode 100644
index 000000000..58b9a18af
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_disable.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that the 'New Tab Page' feature can be disabled if the
+ * decides not to use it.
+ */
+add_task(function* () {
+ // create a new tab page and hide it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ let firstTab = yield* addNewTabPageTab();
+
+ function isGridDisabled(browser = gBrowser.selectedBrowser)
+ {
+ return ContentTask.spawn(browser, {}, function*() {
+ return content.gGrid.node.hasAttribute("page-disabled");
+ });
+ }
+
+ let isDisabled = yield isGridDisabled();
+ ok(!isDisabled, "page is not disabled");
+
+ NewTabUtils.allPages.enabled = false;
+
+ isDisabled = yield isGridDisabled();
+ ok(isDisabled, "page is disabled");
+
+ // create a second new tab page and make sure it's disabled. enable it
+ // again and check if the former page gets enabled as well.
+ yield* addNewTabPageTab();
+ isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
+ ok(isDisabled, "page is disabled");
+
+ // check that no sites have been rendered
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+ Assert.equal(content.document.querySelectorAll(".site").length, 0,
+ "no sites have been rendered");
+ });
+
+ NewTabUtils.allPages.enabled = true;
+
+ isDisabled = yield isGridDisabled();
+ ok(!isDisabled, "page is not disabled");
+
+ isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
+ ok(!isDisabled, "old page is not disabled");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop.js b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
new file mode 100644
index 000000000..da9d89de7
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that dragging and dropping sites works as expected.
+ * Sites contained in the grid need to shift around to indicate the result
+ * of the drag-and-drop operation. If the grid is full and we're dragging
+ * a new site into it another one gets pushed out.
+ */
+add_task(function* () {
+ requestLongerTimeout(2);
+ yield* addNewTabPageTab();
+
+ // test a simple drag-and-drop scenario
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield doDragEvent(0, 1);
+ yield* checkGrid("1,0p,2,3,4,5,6,7,8");
+
+ // drag a cell to its current cell and make sure it's not pinned afterwards
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield doDragEvent(0, 0);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // ensure that pinned pages aren't moved if that's not necessary
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1,2");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1p,2p,3,4,5,6,7,8");
+
+ yield doDragEvent(0, 3);
+ yield* checkGrid("3,1p,2p,0p,4,5,6,7,8");
+
+ // pinned sites should always be moved around as blocks. if a pinned site is
+ // moved around, neighboring pinned are affected as well
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2,3,4,5,6,7,8");
+
+ yield doDragEvent(2, 0);
+ yield* checkGrid("2p,0p,1p,3,4,5,6,7,8");
+
+ // pinned sites should not be pushed out of the grid (unless there are only
+ // pinned ones left on the grid)
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield doDragEvent(2, 5);
+ yield* checkGrid("0,1,3,4,5,2p,6,7p,8p");
+
+ // make sure that pinned sites are re-positioned correctly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,5");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+ yield doDragEvent(0, 4);
+ yield* checkGrid("3,1p,2p,4,0p,5p,6,7,8");
+});
+
+function doDragEvent(sourceIndex, dropIndex) {
+ return ContentTask.spawn(gBrowser.selectedBrowser,
+ { sourceIndex: sourceIndex, dropIndex: dropIndex }, function*(args) {
+ let dataTransfer = new content.DataTransfer("dragstart", false);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("dragstart", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[args.sourceIndex].site.node;
+ target.dispatchEvent(event);
+
+ event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ target = content.gGrid.cells[args.dropIndex].node;
+ target.dispatchEvent(event);
+ });
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
new file mode 100644
index 000000000..4e7b062cb
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+
+/*
+ * These tests make sure that dragging and dropping sites works as expected.
+ * Sites contained in the grid need to shift around to indicate the result
+ * of the drag-and-drop operation. If the grid is full and we're dragging
+ * a new site into it another one gets pushed out.
+ * This is a continuation of browser_newtab_drag_drop.js
+ * to decrease test run time, focusing on external sites.
+ */
+ add_task(function* () {
+ yield* addNewTabPageTab();
+
+ // drag a new site onto the very first cell
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield* simulateExternalDrop(0);
+ yield* checkGrid("99p,0,1,2,3,4,5,7p,8p");
+
+ // drag a new site onto the grid and make sure that pinned cells don't get
+ // pushed out
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ // force the grid to be small enough that a pinned cell could be pushed out
+ yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
+ yield* simulateExternalDrop(5);
+ yield* checkGrid("0,1,2,3,4,99p,5,7p,8p");
+
+ // drag a new site beneath a pinned cell and make sure the pinned cell is
+ // not moved
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield* simulateExternalDrop(5);
+ yield* checkGrid("0,1,2,3,4,99p,5,6,8p");
+
+ // drag a new site onto a block of pinned sites and make sure they're shifted
+ // around accordingly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,,,,");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2p");
+
+ yield* simulateExternalDrop(1);
+ yield* checkGrid("0p,99p,1p,2p,3,4,5,6,7");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_drop_preview.js b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
new file mode 100644
index 000000000..f9e37f629
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests ensure that the drop preview correctly arranges sites when
+ * dragging them around.
+ */
+add_task(function* () {
+ yield* addNewTabPageTab();
+
+ // the first three sites are pinned - make sure they're re-arranged correctly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,5");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+ let foundSites = yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
+ let cells = content.gGrid.cells;
+ content.gDrag._draggedSite = cells[0].site;
+ let sites = content.gDropPreview.rearrange(cells[4]);
+ content.gDrag._draggedSite = null;
+
+ sites = sites.slice(0, 9);
+ return sites.map(function (aSite) {
+ if (!aSite)
+ return "";
+
+ let pinned = aSite.isPinned();
+ if (pinned != aSite.node.hasAttribute("pinned")) {
+ Assert.ok(false, "invalid state (site.isPinned() != site[pinned])");
+ }
+
+ return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+ });
+ });
+
+ let expectedSites = "3,1p,2p,4,0p,5p,6,7,8"
+ is(foundSites, expectedSites, "grid status = " + expectedSites);
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_enhanced.js b/browser/base/content/test/newtab/browser_newtab_enhanced.js
new file mode 100644
index 000000000..5ac07ce55
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -0,0 +1,228 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+const PRELOAD_PREF = "browser.newtab.preload";
+
+var suggestedLink = {
+ url: "http://example1.com/2",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ title: "title2",
+ type: "affiliate",
+ adgroup_name: "Technology",
+ frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"]
+};
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "enhanced": [{
+ url: "http://example.com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD",
+ title: "title",
+ type: "organic",
+ }],
+ "directory": [{
+ url: "http://example1.com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title1",
+ type: "organic"
+ }],
+ "suggested": [suggestedLink]
+});
+
+add_task(function* () {
+ let origEnhanced = NewTabUtils.allPages.enhanced;
+ registerCleanupFunction(() => {
+ NewTabUtils.allPages.enhanced = origEnhanced;
+ });
+
+ yield pushPrefs([PRELOAD_PREF, false]);
+
+ function getData(cellNum) {
+ return performOnCell(cellNum, cell => {
+ if (!cell.site)
+ return null;
+ let siteNode = cell.site.node;
+ return {
+ type: siteNode.getAttribute("type"),
+ enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+ title: siteNode.querySelector(".newtab-title").textContent,
+ suggested: siteNode.querySelector(".newtab-suggested").innerHTML
+ };
+ });
+ }
+
+ // Make the page have a directory link, enhanced link, and history link
+ yield setLinks("-1");
+
+ // Test with enhanced = false
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("classic");
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+ let {type, enhanced, title, suggested} = yield getData(0);
+ isnot(type, "enhanced", "history link is not enhanced");
+ is(enhanced, "", "history link has no enhanced image");
+ is(title, "example.com");
+ is(suggested, "", "There is no suggested explanation");
+
+ let data = yield getData(1);
+ is(data, null, "there is only one link and it's a history link");
+
+ // Test with enhanced = true
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced on
+ ({type, enhanced, title, suggested} = yield getData(0));
+ is(type, "organic", "directory link is organic");
+ isnot(enhanced, "", "directory link has enhanced image");
+ is(title, "title1");
+ is(suggested, "", "There is no suggested explanation");
+
+ ({type, enhanced, title, suggested} = yield getData(1));
+ is(type, "enhanced", "history link is enhanced");
+ isnot(enhanced, "", "history link has enhanced image");
+ is(title, "title");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(2);
+ is(data, null, "there are only 2 links, directory and enhanced history");
+
+ // Test with a pinned link
+ setPinnedLinks("-1");
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ is(type, "enhanced", "pinned history link is enhanced");
+ isnot(enhanced, "", "pinned history link has enhanced image");
+ is(title, "title");
+ is(suggested, "", "There is no suggested explanation");
+
+ ({type, enhanced, title, suggested} = yield getData(1));
+ is(type, "organic", "directory link is organic");
+ isnot(enhanced, "", "directory link has enhanced image");
+ is(title, "title1");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(2);
+ is(data, null, "directory link pushed out by pinned history link");
+
+ // Test pinned link with enhanced = false
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+ ({type, enhanced, title, suggested} = yield getData(0));
+ isnot(type, "enhanced", "history link is not enhanced");
+ is(enhanced, "", "history link has no enhanced image");
+ is(title, "example.com");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(1);
+ is(data, null, "directory link still pushed out by pinned history link");
+
+ yield unpinCell(0);
+
+
+
+ // Test that a suggested tile is not enhanced by a directory tile
+ NewTabUtils.isTopPlacesSite = () => true;
+ yield setLinks("-1,2,3,4,5,6,7,8");
+
+ // Test with enhanced = false
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ isnot(type, "enhanced", "history link is not enhanced");
+ is(enhanced, "", "history link has no enhanced image");
+ is(title, "example.com");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(7);
+ isnot(data, null, "there are 8 history links");
+ data = yield getData(8);
+ is(data, null, "there are 8 history links");
+
+
+ // Test with enhanced = true
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced");
+
+ // Suggested link was not enhanced by directory link with same domain
+ ({type, enhanced, title, suggested} = yield getData(0));
+ is(type, "affiliate", "suggested link is affiliate");
+ is(enhanced, "", "suggested link has no enhanced image");
+ is(title, "title2");
+ ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
+
+ // Enhanced history link shows up second
+ ({type, enhanced, title, suggested} = yield getData(1));
+ is(type, "enhanced", "pinned history link is enhanced");
+ isnot(enhanced, "", "pinned history link has enhanced image");
+ is(title, "title");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(9);
+ is(data, null, "there is a suggested link followed by an enhanced history link and the remaining history links");
+
+
+
+ // Test no override category/adgroup name.
+ let linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + JSON.stringify({"suggested": [suggestedLink]})]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
+
+
+ // Test server provided explanation string.
+ suggestedLink.explanation = "Suggested for %1$S enthusiasts who visit sites like %2$S";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
+
+
+ // Test server provided explanation string with category override.
+ suggestedLink.adgroup_name = "webdev education";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
+
+
+
+ // Test with xml entities in category name
+ suggestedLink.url = "http://example1.com/3";
+ suggestedLink.adgroup_name = ">angles< & \"quotes\'";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> &gt;angles&lt; &amp; \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts");
+
+
+ // Test with xml entities in explanation.
+ suggestedLink.explanation = "Testing junk explanation &<>\"'";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Testing junk explanation &amp;&lt;&gt;\"'") > -1, "Junk test");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_focus.js b/browser/base/content/test/newtab/browser_newtab_focus.js
new file mode 100644
index 000000000..ae0dd8d29
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that focusing the 'New Tab Page' works as expected.
+ */
+add_task(function* () {
+ yield pushPrefs(["accessibility.tabfocus", 7]);
+
+ // Focus count in new tab page.
+ // 30 = 9 * 3 + 3 = 9 sites, each with link, pin and remove buttons; search
+ // bar; search button; and toggle button. Additionaly there may or may not be
+ // a scroll bar caused by fix to 1180387, which will eat an extra focus
+ let FOCUS_COUNT = 30;
+
+ // Create a new tab page.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ gURLBar.focus();
+
+ // Count the focus with the enabled page.
+ countFocus(FOCUS_COUNT);
+
+ // Disable page and count the focus with the disabled page.
+ NewTabUtils.allPages.enabled = false;
+
+ countFocus(4);
+
+ NewTabUtils.allPages.enabled = true;
+});
+
+/**
+ * Focus the urlbar and count how many focus stops to return again to the urlbar.
+ */
+function countFocus(aExpectedCount) {
+ let focusCount = 0;
+ do {
+ EventUtils.synthesizeKey("VK_TAB", {});
+ if (document.activeElement == gBrowser.selectedBrowser) {
+ focusCount++;
+ }
+ } while (document.activeElement != gURLBar.inputField);
+
+ ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
+ "Validate focus count in the new tab page.");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
new file mode 100644
index 000000000..b330bec13
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests ensure that all changes made to the new tab page in private
+ * browsing mode are discarded after switching back to normal mode again.
+ * The private browsing mode should start with the current grid shown in normal
+ * mode.
+ */
+
+add_task(function* () {
+ // prepare the grid
+ yield testOnWindow(undefined);
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+
+ yield* addNewTabPageTab();
+ yield pinCell(0);
+ yield* checkGrid("0p,1,2,3,4,5,6,7,8");
+
+ // open private window
+ yield testOnWindow({private: true});
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1,2,3,4,5,6,7,8");
+
+ // modify the grid while we're in pb mode
+ yield blockCell(1);
+ yield* checkGrid("0p,2,3,4,5,6,7,8");
+
+ yield unpinCell(0);
+ yield* checkGrid("0,2,3,4,5,6,7,8");
+
+ // open normal window
+ yield testOnWindow(undefined);
+
+ // check that the grid is the same as before entering pb mode
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,2,3,4,5,6,7,8")
+});
+
+var windowsToClose = [];
+function* testOnWindow(options) {
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ var win = OpenBrowserWindow(options);
+ windowsToClose.push(win);
+ gWindow = win;
+ yield newWindowPromise;
+}
+
+registerCleanupFunction(function () {
+ gWindow = window;
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_reflow_load.js b/browser/base/content/test/newtab/browser_newtab_reflow_load.js
new file mode 100644
index 000000000..b8a24595e
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_reflow_load.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FRAME_SCRIPT = getRootDirectory(gTestPath) + "content-reflows.js";
+const ADDITIONAL_WAIT_MS = 2000;
+
+/*
+ * Ensure that loading about:newtab doesn't cause uninterruptible reflows.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ return gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
+ }, false);
+
+ let browser = gBrowser.selectedBrowser;
+ let mm = browser.messageManager;
+ mm.loadFrameScript(FRAME_SCRIPT, true);
+ mm.addMessageListener("newtab-reflow", ({data: stack}) => {
+ ok(false, `unexpected uninterruptible reflow ${stack}`);
+ });
+
+ let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true);
+ browser.loadURI("about:newtab");
+ yield browserLoadedPromise;
+
+ // Wait some more to catch sync reflows after the page has loaded.
+ yield new Promise(resolve => {
+ setTimeout(resolve, ADDITIONAL_WAIT_MS);
+ });
+
+ // Clean up.
+ gBrowser.removeCurrentTab({animate: false});
+
+ ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
new file mode 100644
index 000000000..24e1be369
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PRELOAD_PREF = "browser.newtab.preload";
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "directory": [{
+ url: "http://example.com/organic",
+ type: "organic"
+ }, {
+ url: "http://localhost/sponsored",
+ type: "sponsored"
+ }]
+});
+
+add_task(function* () {
+ yield pushPrefs([PRELOAD_PREF, false]);
+
+ let originalReportSitesAction = DirectoryLinksProvider.reportSitesAction;
+ registerCleanupFunction(() => {
+ DirectoryLinksProvider.reportSitesAction = originalReportSitesAction;
+ });
+
+ let expected = {};
+
+ function expectReportSitesAction() {
+ return new Promise(resolve => {
+ DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
+ let {link} = sites[siteIndex];
+ is(link.type, expected.type, "got expected type");
+ is(action, expected.action, "got expected action");
+ is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
+ resolve();
+ }
+ });
+ }
+
+ // Test that the last visible site (index 1) is reported
+ let reportSitesPromise = expectReportSitesAction();
+ expected.type = "sponsored";
+ expected.action = "view";
+ expected.pinned = false;
+ yield* addNewTabPageTab();
+ yield reportSitesPromise;
+
+ // Click the pin button on the link in the 1th tile spot
+ expected.action = "pin";
+ // tiles become "history" when pinned
+ expected.type = "history";
+ expected.pinned = true;
+ let pagesUpdatedPromise = whenPagesUpdated();
+ reportSitesPromise = expectReportSitesAction();
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield pagesUpdatedPromise;
+ yield reportSitesPromise;
+
+ // Unpin that link
+ expected.action = "unpin";
+ expected.pinned = false;
+ pagesUpdatedPromise = whenPagesUpdated();
+ reportSitesPromise = expectReportSitesAction();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield pagesUpdatedPromise;
+ yield reportSitesPromise;
+
+ // Block the site in the 0th tile spot
+ expected.type = "organic";
+ expected.action = "block";
+ expected.pinned = false;
+ pagesUpdatedPromise = whenPagesUpdated();
+ reportSitesPromise = expectReportSitesAction();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site .newtab-control-block", {}, gBrowser.selectedBrowser);
+ yield pagesUpdatedPromise;
+ yield reportSitesPromise;
+
+ // Click the 1th link now in the 0th tile spot
+ expected.type = "history";
+ expected.action = "click";
+ reportSitesPromise = expectReportSitesAction();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
+ yield reportSitesPromise;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_search.js b/browser/base/content/test/newtab/browser_newtab_search.js
new file mode 100644
index 000000000..19ed4ba74
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -0,0 +1,247 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// See browser/components/search/test/browser_*_behavior.js for tests of actual
+// searches.
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+const ENGINE_NO_LOGO = {
+ name: "searchEngineNoLogo.xml",
+ numLogos: 0,
+};
+
+const ENGINE_FAVICON = {
+ name: "searchEngineFavicon.xml",
+ logoPrefix1x: "data:image/png;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsA",
+ numLogos: 1,
+};
+ENGINE_FAVICON.logoPrefix2x = ENGINE_FAVICON.logoPrefix1x;
+
+const ENGINE_1X_LOGO = {
+ name: "searchEngine1xLogo.xml",
+ logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEw",
+ numLogos: 1,
+};
+ENGINE_1X_LOGO.logoPrefix2x = ENGINE_1X_LOGO.logoPrefix1x;
+
+const ENGINE_2X_LOGO = {
+ name: "searchEngine2xLogo.xml",
+ logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMU",
+ numLogos: 1,
+};
+ENGINE_2X_LOGO.logoPrefix1x = ENGINE_2X_LOGO.logoPrefix2x;
+
+const ENGINE_1X_2X_LOGO = {
+ name: "searchEngine1x2xLogo.xml",
+ logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIG",
+ logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMo",
+ numLogos: 2,
+};
+
+const ENGINE_SUGGESTIONS = {
+ name: "searchSuggestionEngine.xml",
+ numLogos: 0,
+};
+
+// The test has an expected search event queue and a search event listener.
+// Search events that are expected to happen are added to the queue, and the
+// listener consumes the queue and ensures that each event it receives is at
+// the head of the queue.
+let gExpectedSearchEventQueue = [];
+let gExpectedSearchEventResolver = null;
+
+let gNewEngines = [];
+
+add_task(function* () {
+ let oldCurrentEngine = Services.search.currentEngine;
+
+ yield* addNewTabPageTab();
+
+ // The tab is removed at the end of the test, so there's no need to remove
+ // this listener at the end of the test.
+ info("Adding search event listener");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ const SERVICE_EVENT_NAME = "ContentSearchService";
+ content.addEventListener(SERVICE_EVENT_NAME, function (event) {
+ sendAsyncMessage("test:search-event", { eventType: event.detail.type });
+ });
+ });
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("test:search-event", function (message) {
+ let eventType = message.data.eventType;
+ if (!gExpectedSearchEventResolver) {
+ ok(false, "Got search event " + eventType + " with no promise assigned");
+ }
+
+ let expectedEventType = gExpectedSearchEventQueue.shift();
+ is(eventType, expectedEventType, "Got expected search event " + expectedEventType);
+ if (!gExpectedSearchEventQueue.length) {
+ gExpectedSearchEventResolver();
+ gExpectedSearchEventResolver = null;
+ }
+ });
+
+ // Add the engine without any logos and switch to it.
+ let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
+ let searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = noLogoEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_NO_LOGO);
+
+ // Add the engine with favicon and switch to it.
+ let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = faviconEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_FAVICON);
+
+ // Add the engine with a 1x-DPI logo and switch to it.
+ let logo1xEngine = yield promiseNewSearchEngine(ENGINE_1X_LOGO);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = logo1xEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_1X_LOGO);
+
+ // Add the engine with a 2x-DPI logo and switch to it.
+ let logo2xEngine = yield promiseNewSearchEngine(ENGINE_2X_LOGO);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = logo2xEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_2X_LOGO);
+
+ // Add the engine with 1x- and 2x-DPI logos and switch to it.
+ let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = logo1x2xEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_1X_2X_LOGO);
+
+ // Add the engine that provides search suggestions and switch to it.
+ let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = suggestionEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_SUGGESTIONS);
+
+ // Avoid intermittent failures.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.gSearch._contentSearchController.remoteTimeout = 5000;
+ });
+
+ // Type an X in the search input. This is only a smoke test. See
+ // browser_searchSuggestionUI.js for comprehensive content search suggestion
+ // UI tests.
+ let suggestionsOpenPromise = new Promise(resolve => {
+ mm.addMessageListener("test:newtab-suggestions-open", function onResponse(message) {
+ mm.removeMessageListener("test:newtab-suggestions-open", onResponse);
+ resolve();
+ });
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let table = content.document.getElementById("searchSuggestionTable");
+
+ let input = content.document.getElementById("newtab-search-text");
+ input.focus();
+
+ info("Waiting for suggestions table to open");
+ let observer = new content.MutationObserver(() => {
+ if (input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ Assert.ok(!table.hidden, "Search suggestion table unhidden");
+ sendAsyncMessage("test:newtab-suggestions-open", {});
+ }
+ });
+ observer.observe(input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+ });
+
+ let suggestionsPromise = promiseSearchEvents(["Suggestions"]);
+
+ EventUtils.synthesizeKey("x", {});
+
+ // Wait for the search suggestions to become visible and for the Suggestions
+ // message.
+ yield suggestionsOpenPromise;
+ yield suggestionsPromise;
+
+ // Empty the search input, causing the suggestions to be hidden.
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ EventUtils.synthesizeKey("VK_DELETE", {});
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ Assert.ok(content.document.getElementById("searchSuggestionTable").hidden,
+ "Search suggestion table hidden");
+ });
+
+ // Done. Revert the current engine and remove the new engines.
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = oldCurrentEngine;
+ yield searchEventsPromise;
+
+ let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length);
+ searchEventsPromise = promiseSearchEvents(events);
+
+ for (let engine of gNewEngines) {
+ Services.search.removeEngine(engine);
+ }
+ yield searchEventsPromise;
+});
+
+function promiseSearchEvents(events) {
+ info("Expecting search events: " + events);
+ return new Promise(resolve => {
+ gExpectedSearchEventQueue.push(...events);
+ gExpectedSearchEventResolver = resolve;
+ });
+}
+
+function promiseNewSearchEngine({name: basename, numLogos}) {
+ info("Waiting for engine to be added: " + basename);
+
+ // Wait for the search events triggered by adding the new engine.
+ // engine-added engine-loaded
+ let expectedSearchEvents = ["CurrentState", "CurrentState"];
+ // engine-changed for each of the logos
+ for (let i = 0; i < numLogos; i++) {
+ expectedSearchEvents.push("CurrentState");
+ }
+ let eventPromise = promiseSearchEvents(expectedSearchEvents);
+
+ // Wait for addEngine().
+ let addEnginePromise = new Promise((resolve, reject) => {
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ gNewEngines.push(engine);
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+
+ return Promise.all([addEnginePromise, eventPromise]).then(([newEngine, _]) => {
+ return newEngine;
+ });
+}
+
+function* checkCurrentEngine(engineInfo)
+{
+ let engine = Services.search.currentEngine;
+ ok(engine.name.includes(engineInfo.name),
+ "Sanity check: current engine: engine.name=" + engine.name +
+ " basename=" + engineInfo.name);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { name: engine.name }, function* (args) {
+ Assert.equal(content.gSearch._contentSearchController.defaultEngine.name,
+ args.name, "currentEngineName: " + args.name);
+ });
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
new file mode 100644
index 000000000..f6bb85d47
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0");
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ var EventUtils = {};
+ EventUtils.window = {};
+ EventUtils.parent = EventUtils.window;
+ EventUtils._EU_Ci = Components.interfaces;
+
+ Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ let cell = content.gGrid.cells[0];
+
+ let site = cell.node.querySelector(".newtab-site");
+ site.setAttribute("type", "sponsored");
+
+ // test explain text appearing upon a click
+ let sponsoredButton = site.querySelector(".newtab-sponsored");
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ let explain = site.querySelector(".sponsored-explain");
+ Assert.notEqual(explain, null, "Sponsored explanation shown");
+ Assert.ok(explain.querySelector("input").classList.contains("newtab-control-block"),
+ "sponsored tiles show blocked image");
+ Assert.ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+
+ // test dismissing sponsored explain
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ Assert.equal(site.querySelector(".sponsored-explain"), null,
+ "Sponsored explanation no longer shown");
+ Assert.ok(!sponsoredButton.hasAttribute("active"),
+ "Sponsored button does not have active attribute");
+
+ // test with enhanced tile
+ site.setAttribute("type", "enhanced");
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ explain = site.querySelector(".sponsored-explain");
+ Assert.notEqual(explain, null, "Sponsored explanation shown");
+ Assert.ok(explain.querySelector("input").classList.contains("newtab-customize"),
+ "enhanced tiles show customize image");
+ Assert.ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+
+ // test dismissing enhanced explain
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ Assert.equal(site.querySelector(".sponsored-explain"), null,
+ "Sponsored explanation no longer shown");
+ Assert.ok(!sponsoredButton.hasAttribute("active"),
+ "Sponsored button does not have active attribute");
+ });
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_undo.js b/browser/base/content/test/newtab/browser_newtab_undo.js
new file mode 100644
index 000000000..ba094cb26
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_undo.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that the undo dialog works as expected.
+ */
+add_task(function* () {
+ // remove unpinned sites and undo it
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("5");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("5p,0,1,2,3,4,6,7,8");
+
+ yield blockCell(4);
+ yield blockCell(4);
+ yield* checkGrid("5p,0,1,2,6,7,8");
+
+ yield* undo();
+ yield* checkGrid("5p,0,1,2,4,6,7,8");
+
+ // now remove a pinned site and undo it
+ yield blockCell(0);
+ yield* checkGrid("0,1,2,4,6,7,8");
+
+ yield* undo();
+ yield* checkGrid("5p,0,1,2,4,6,7,8");
+
+ // remove a site and restore all
+ yield blockCell(1);
+ yield* checkGrid("5p,1,2,4,6,7,8");
+
+ yield* undoAll();
+ yield* checkGrid("5p,0,1,2,3,4,6,7,8");
+});
+
+function* undo() {
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-button", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+}
+
+function* undoAll() {
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-restore-button", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_unpin.js b/browser/base/content/test/newtab/browser_newtab_unpin.js
new file mode 100644
index 000000000..14751465f
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_unpin.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that when a site gets unpinned it is either moved to
+ * its actual place in the grid or removed in case it's not on the grid anymore.
+ */
+add_task(function* () {
+ // we have a pinned link that didn't change its position since it was pinned.
+ // nothing should happen when we unpin it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ yield unpinCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // we have a pinned link that is not anymore in the list of the most-visited
+ // links. this should disappear, the remaining links adjust their positions
+ // and a new link will appear at the end of the grid.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",99");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield unpinCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // we have a pinned link that changed its position since it was pinned. it
+ // should be moved to its new position after being unpinned.
+ yield setLinks("0,1,2,3,4,5,6,7");
+ setPinnedLinks(",1,,,,,,,0");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("2,1p,3,4,5,6,7,,0p");
+
+ yield unpinCell(1);
+ yield* checkGrid("1,2,3,4,5,6,7,,0p");
+
+ yield unpinCell(8);
+ yield* checkGrid("0,1,2,3,4,5,6,7,");
+
+ // we have pinned link that changed its position since it was pinned. the
+ // link will disappear from the grid because it's now a much lower priority
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("9");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("9p,0,1,2,3,4,5,6,7");
+
+ yield unpinCell(0);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_update.js b/browser/base/content/test/newtab/browser_newtab_update.js
new file mode 100644
index 000000000..6cf089dfd
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_update.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Checks that newtab is updated as its links change.
+ */
+add_task(function* () {
+ // First, start with an empty page. setLinks will trigger a hidden page
+ // update because it calls clearHistory. We need to wait for that update to
+ // happen so that the next time we wait for a page update below, we catch the
+ // right update and not the one triggered by setLinks.
+ let updatedPromise = whenPagesUpdated();
+ let setLinksPromise = setLinks([]);
+ yield Promise.all([updatedPromise, setLinksPromise]);
+
+ // Strategy: Add some visits, open a new page, check the grid, repeat.
+ yield fillHistoryAndWaitForPageUpdate([1]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("1,,,,,,,,");
+
+ yield fillHistoryAndWaitForPageUpdate([2]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("2,1,,,,,,,");
+
+ yield fillHistoryAndWaitForPageUpdate([1]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("1,2,,,,,,,");
+
+ yield fillHistoryAndWaitForPageUpdate([2, 3, 4]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("2,1,3,4,,,,,");
+
+ // Make sure these added links have the right type
+ let type = yield performOnCell(1, cell => { return cell.site.link.type });
+ is(type, "history", "added link is history");
+});
+
+function fillHistoryAndWaitForPageUpdate(links) {
+ let updatedPromise = whenPagesUpdated;
+ let fillHistoryPromise = fillHistory(links.map(link));
+ return Promise.all([updatedPromise, fillHistoryPromise]);
+}
+
+function link(id) {
+ return { url: "http://example" + id + ".com/", title: "site#" + id };
+}
diff --git a/browser/base/content/test/newtab/content-reflows.js b/browser/base/content/test/newtab/content-reflows.js
new file mode 100644
index 000000000..f1a53782e
--- /dev/null
+++ b/browser/base/content/test/newtab/content-reflows.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+(function () {
+ "use strict";
+
+ const Ci = Components.interfaces;
+
+ docShell.addWeakReflowObserver({
+ reflow() {
+ // Gather information about the current code path.
+ let path = (new Error().stack).split("\n").slice(1).join("\n");
+ if (path) {
+ sendSyncMessage("newtab-reflow", path);
+ }
+ },
+
+ reflowInterruptible() {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+ });
+})();
diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js
new file mode 100644
index 000000000..d702103a0
--- /dev/null
+++ b/browser/base/content/test/newtab/head.js
@@ -0,0 +1,552 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
+const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
+
+Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
+
+var tmp = {};
+Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
+Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
+Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+var {NewTabUtils, Sanitizer, DirectoryLinksProvider, PlacesTestUtils} = tmp;
+
+var gWindow = window;
+
+// Default to dummy/empty directory links
+var gDirectorySource = 'data:application/json,{"test":1}';
+var gOrigDirectorySource;
+
+// The tests assume all 3 rows and all 3 columns of sites are shown, but the
+// window may be too small to actually show everything. Resize it if necessary.
+var requiredSize = {};
+requiredSize.innerHeight =
+ 40 + 32 + // undo container + bottom margin
+ 44 + 32 + // search bar + bottom margin
+ (3 * (180 + 32)) + // 3 rows * (tile height + title and bottom margin)
+ 100; // breathing room
+requiredSize.innerWidth =
+ (3 * (290 + 20)) + // 3 cols * (tile width + side margins)
+ 100; // breathing room
+
+var oldSize = {};
+Object.keys(requiredSize).forEach(prop => {
+ info([prop, gBrowser.contentWindow[prop], requiredSize[prop]]);
+ if (gBrowser.contentWindow[prop] < requiredSize[prop]) {
+ oldSize[prop] = gBrowser.contentWindow[prop];
+ info("Changing browser " + prop + " from " + oldSize[prop] + " to " +
+ requiredSize[prop]);
+ gBrowser.contentWindow[prop] = requiredSize[prop];
+ }
+});
+
+var screenHeight = {};
+var screenWidth = {};
+Cc["@mozilla.org/gfx/screenmanager;1"].
+ getService(Ci.nsIScreenManager).
+ primaryScreen.
+ GetAvailRectDisplayPix({}, {}, screenWidth, screenHeight);
+screenHeight = screenHeight.value;
+screenWidth = screenWidth.value;
+
+if (screenHeight < gBrowser.contentWindow.outerHeight) {
+ info("Warning: Browser outer height is now " +
+ gBrowser.contentWindow.outerHeight + ", which is larger than the " +
+ "available screen height, " + screenHeight +
+ ". That may cause problems.");
+}
+
+if (screenWidth < gBrowser.contentWindow.outerWidth) {
+ info("Warning: Browser outer width is now " +
+ gBrowser.contentWindow.outerWidth + ", which is larger than the " +
+ "available screen width, " + screenWidth +
+ ". That may cause problems.");
+}
+
+registerCleanupFunction(function () {
+ while (gWindow.gBrowser.tabs.length > 1)
+ gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
+
+ Object.keys(oldSize).forEach(prop => {
+ if (oldSize[prop]) {
+ gBrowser.contentWindow[prop] = oldSize[prop];
+ }
+ });
+
+ // Stop any update timers to prevent unexpected updates in later tests
+ let timer = NewTabUtils.allPages._scheduleUpdateTimeout;
+ if (timer) {
+ clearTimeout(timer);
+ delete NewTabUtils.allPages._scheduleUpdateTimeout;
+ }
+
+ Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
+
+ return watchLinksChangeOnce();
+});
+
+function pushPrefs(...aPrefs) {
+ return new Promise(resolve =>
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve));
+}
+
+/**
+ * Resolves promise when directory links are downloaded and written to disk
+ */
+function watchLinksChangeOnce() {
+ return new Promise(resolve => {
+ let observer = {
+ onManyLinksChanged: () => {
+ DirectoryLinksProvider.removeObserver(observer);
+ resolve();
+ }
+ };
+ observer.onDownloadFail = observer.onManyLinksChanged;
+ DirectoryLinksProvider.addObserver(observer);
+ });
+}
+
+add_task(function* setup() {
+ registerCleanupFunction(function() {
+ return new Promise(resolve => {
+ function cleanupAndFinish() {
+ PlacesTestUtils.clearHistory().then(() => {
+ whenPagesUpdated().then(resolve);
+ NewTabUtils.restore();
+ });
+ }
+
+ let callbacks = NewTabUtils.links._populateCallbacks;
+ let numCallbacks = callbacks.length;
+
+ if (numCallbacks)
+ callbacks.splice(0, numCallbacks, cleanupAndFinish);
+ else
+ cleanupAndFinish();
+ });
+ });
+
+ let promiseReady = Task.spawn(function*() {
+ yield watchLinksChangeOnce();
+ yield whenPagesUpdated();
+ });
+
+ // Save the original directory source (which is set globally for tests)
+ gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
+ yield promiseReady;
+});
+
+/** Perform an action on a cell within the newtab page.
+ * @param aIndex index of cell
+ * @param aFn function to call in child process or tab.
+ * @returns result of calling the function.
+ */
+function performOnCell(aIndex, aFn) {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
+ { index: aIndex, fn: aFn.toString() }, function* (args) {
+ let cell = content.gGrid.cells[args.index];
+ return eval(args.fn)(cell);
+ });
+}
+
+/**
+ * Allows to provide a list of links that is used to construct the grid.
+ * @param aLinksPattern the pattern (see below)
+ *
+ * Example: setLinks("-1,0,1,2,3")
+ * Result: [{url: "http://example.com/", title: "site#-1"},
+ * {url: "http://example0.com/", title: "site#0"},
+ * {url: "http://example1.com/", title: "site#1"},
+ * {url: "http://example2.com/", title: "site#2"},
+ * {url: "http://example3.com/", title: "site#3"}]
+ */
+function setLinks(aLinks) {
+ return new Promise(resolve => {
+ let links = aLinks;
+
+ if (typeof links == "string") {
+ links = aLinks.split(/\s*,\s*/).map(function (id) {
+ return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+ title: "site#" + id};
+ });
+ }
+
+ // Call populateCache() once to make sure that all link fetching that is
+ // currently in progress has ended. We clear the history, fill it with the
+ // given entries and call populateCache() now again to make sure the cache
+ // has the desired contents.
+ NewTabUtils.links.populateCache(function () {
+ PlacesTestUtils.clearHistory().then(() => {
+ fillHistory(links).then(() => {
+ NewTabUtils.links.populateCache(function () {
+ NewTabUtils.allPages.update();
+ resolve();
+ }, true);
+ });
+ });
+ });
+ });
+}
+
+function fillHistory(aLinks) {
+ return new Promise(resolve => {
+ let numLinks = aLinks.length;
+ if (!numLinks) {
+ executeSoon(resolve);
+ return;
+ }
+
+ let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
+
+ // Important: To avoid test failures due to clock jitter on Windows XP, call
+ // Date.now() once here, not each time through the loop.
+ let now = Date.now() * 1000;
+
+ for (let i = 0; i < aLinks.length; i++) {
+ let link = aLinks[i];
+ let place = {
+ uri: makeURI(link.url),
+ title: link.title,
+ // Links are secondarily sorted by visit date descending, so decrease the
+ // visit date as we progress through the array so that links appear in the
+ // grid in the order they're present in the array.
+ visits: [{visitDate: now - i, transitionType: transitionLink}]
+ };
+
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: () => ok(false, "couldn't add visit to history"),
+ handleResult: function () {},
+ handleCompletion: function () {
+ if (--numLinks == 0) {
+ resolve();
+ }
+ }
+ });
+ }
+ });
+}
+
+/**
+ * Allows to specify the list of pinned links (that have a fixed position in
+ * the grid.
+ * @param aLinksPattern the pattern (see below)
+ *
+ * Example: setPinnedLinks("3,,1")
+ * Result: 'http://example3.com/' is pinned in the first cell. 'http://example1.com/' is
+ * pinned in the third cell.
+ */
+function setPinnedLinks(aLinks) {
+ let links = aLinks;
+
+ if (typeof links == "string") {
+ links = aLinks.split(/\s*,\s*/).map(function (id) {
+ if (id)
+ return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+ title: "site#" + id,
+ type: "history"};
+ return undefined;
+ });
+ }
+
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(links);
+ Services.prefs.setComplexValue("browser.newtabpage.pinned",
+ Ci.nsISupportsString, string);
+
+ NewTabUtils.pinnedLinks.resetCache();
+ NewTabUtils.allPages.update();
+}
+
+/**
+ * Restore the grid state.
+ */
+function restore() {
+ return new Promise(resolve => {
+ whenPagesUpdated().then(resolve);
+ NewTabUtils.restore();
+ });
+}
+
+/**
+ * Wait until a given condition becomes true.
+ */
+function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
+ return new Promise((resolve, reject) => {
+ let tries = 0;
+
+ function tryNow() {
+ tries++;
+
+ if (aConditionFn()) {
+ resolve();
+ } else if (tries < aMaxTries) {
+ tryAgain();
+ } else {
+ reject("Condition timed out: " + aConditionFn.toSource());
+ }
+ }
+
+ function tryAgain() {
+ setTimeout(tryNow, aCheckInterval);
+ }
+
+ tryAgain();
+ });
+}
+
+/**
+ * Creates a new tab containing 'about:newtab'.
+ */
+function* addNewTabPageTab() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gWindow.gBrowser, "about:newtab", false);
+ let browser = tab.linkedBrowser;
+
+ // Wait for the document to become visible in case it was preloaded.
+ yield waitForCondition(() => !browser.contentDocument.hidden)
+
+ yield new Promise(resolve => {
+ if (NewTabUtils.allPages.enabled) {
+ // Continue when the link cache has been populated.
+ NewTabUtils.links.populateCache(function () {
+ whenSearchInitDone().then(resolve);
+ });
+ } else {
+ resolve();
+ }
+ });
+
+ return tab;
+}
+
+/**
+ * Compares the current grid arrangement with the given pattern.
+ * @param the pattern (see below)
+ *
+ * Example: checkGrid("3p,2,,1p")
+ * Result: We expect the first cell to contain the pinned site 'http://example3.com/'.
+ * The second cell contains 'http://example2.com/'. The third cell is empty.
+ * The fourth cell contains the pinned site 'http://example4.com/'.
+ */
+function* checkGrid(pattern) {
+ let length = pattern.split(",").length;
+
+ yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
+ { length, pattern }, function* (args) {
+ let grid = content.wrappedJSObject.gGrid;
+
+ let sites = grid.sites.slice(0, args.length);
+ let foundPattern = sites.map(function (aSite) {
+ if (!aSite)
+ return "";
+
+ let pinned = aSite.isPinned();
+ let hasPinnedAttr = aSite.node.hasAttribute("pinned");
+
+ if (pinned != hasPinnedAttr)
+ ok(false, "invalid state (site.isPinned() != site[pinned])");
+
+ return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+ });
+
+ Assert.equal(foundPattern, args.pattern, "grid status = " + args.pattern);
+ });
+}
+
+/**
+ * Blocks a site from the grid.
+ * @param aIndex The cell index.
+ */
+function blockCell(aIndex) {
+ return new Promise(resolve => {
+ whenPagesUpdated().then(resolve);
+ performOnCell(aIndex, cell => {
+ return cell.site.block();
+ });
+ });
+}
+
+/**
+ * Pins a site on a given position.
+ * @param aIndex The cell index.
+ * @param aPinIndex The index the defines where the site should be pinned.
+ */
+function pinCell(aIndex) {
+ performOnCell(aIndex, cell => {
+ cell.site.pin();
+ });
+}
+
+/**
+ * Unpins the given cell's site.
+ * @param aIndex The cell index.
+ */
+function unpinCell(aIndex) {
+ return new Promise(resolve => {
+ whenPagesUpdated().then(resolve);
+ performOnCell(aIndex, cell => {
+ cell.site.unpin();
+ });
+ });
+}
+
+/**
+ * Simulates a drag and drop operation. Instead of rearranging a site that is
+ * is already contained in the newtab grid, this is used to simulate dragging
+ * an external link onto the grid e.g. the text from the URL bar.
+ * @param aDestIndex The cell index of the drop target.
+ */
+function* simulateExternalDrop(aDestIndex) {
+ let pagesUpdatedPromise = whenPagesUpdated();
+
+ yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aDestIndex, function*(dropIndex) {
+ return new Promise(resolve => {
+ const url = "data:text/html;charset=utf-8," +
+ "<a id='link' href='http://example99.com/'>link</a>";
+
+ let doc = content.document;
+ let iframe = doc.createElement("iframe");
+
+ function iframeLoaded() {
+ let dataTransfer = new iframe.contentWindow.DataTransfer("dragstart", false);
+ dataTransfer.mozSetDataAt("text/x-moz-url", "http://example99.com/", 0);
+
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[dropIndex].node;
+ target.dispatchEvent(event);
+
+ iframe.remove();
+
+ resolve();
+ }
+
+ iframe.addEventListener("load", function onLoad() {
+ iframe.removeEventListener("load", onLoad);
+ content.setTimeout(iframeLoaded, 0);
+ });
+
+ iframe.setAttribute("src", url);
+ iframe.style.width = "50px";
+ iframe.style.height = "50px";
+ iframe.style.position = "absolute";
+ iframe.style.zIndex = 50;
+
+ // the frame has to be attached to a visible element
+ let margin = doc.getElementById("newtab-search-container");
+ margin.appendChild(iframe);
+ });
+ });
+
+ yield pagesUpdatedPromise;
+}
+
+/**
+ * Resumes testing when all pages have been updated.
+ */
+function whenPagesUpdated() {
+ return new Promise(resolve => {
+ let page = {
+ observe: _ => _,
+
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ executeSoon(resolve);
+ }
+ };
+
+ NewTabUtils.allPages.register(page);
+ registerCleanupFunction(function () {
+ NewTabUtils.allPages.unregister(page);
+ });
+ });
+}
+
+/**
+ * Waits for the response to the page's initial search state request.
+ */
+function whenSearchInitDone() {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
+ return new Promise(resolve => {
+ if (content.gSearch) {
+ let searchController = content.gSearch._contentSearchController;
+ if (searchController.defaultEngine) {
+ resolve();
+ return;
+ }
+ }
+
+ let eventName = "ContentSearchService";
+ content.addEventListener(eventName, function onEvent(event) {
+ if (event.detail.type == "State") {
+ content.removeEventListener(eventName, onEvent);
+ let resolver = function() {
+ // Wait for the search controller to receive the event, then resolve.
+ if (content.gSearch._contentSearchController.defaultEngine) {
+ resolve();
+ return;
+ }
+ }
+ content.setTimeout(resolver, 0);
+ }
+ });
+ });
+ });
+}
+
+/**
+ * Changes the newtab customization option and waits for the panel to open and close
+ *
+ * @param {string} aTheme
+ * Can be any of("blank"|"classic"|"enhanced")
+ */
+function customizeNewTabPage(aTheme) {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aTheme, function*(aTheme) {
+
+ let document = content.document;
+ let panel = document.getElementById("newtab-customize-panel");
+ let customizeButton = document.getElementById("newtab-customize-button");
+
+ function panelOpened(opened) {
+ return new Promise( (resolve) => {
+ let options = {attributes: true, oldValue: true};
+ let observer = new content.MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ document.getElementById("newtab-customize-" + aTheme).click();
+ observer.disconnect();
+ if (opened == panel.hasAttribute("open")) {
+ resolve();
+ }
+ });
+ });
+ observer.observe(panel, options);
+ });
+ }
+
+ let opened = panelOpened(true);
+ customizeButton.click();
+ yield opened;
+
+ let closed = panelOpened(false);
+ customizeButton.click();
+ yield closed;
+ });
+}
+
+/**
+ * Reports presence of a scrollbar
+ */
+function hasScrollbar() {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function* () {
+ let docElement = content.document.documentElement;
+ return docElement.scrollHeight > docElement.clientHeight;
+ });
+}
diff --git a/browser/base/content/test/newtab/searchEngine1x2xLogo.xml b/browser/base/content/test/newtab/searchEngine1x2xLogo.xml
new file mode 100644
index 000000000..c8b6749b3
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngine1x2xLogo.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngine1x2xLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1x2xlogo" rel="searchform"/>
+<!-- #00FF00 -->
+<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIGr0+8uwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVRYw+3PAREAAAQEsKd/Z3I4W4NVJtd14uDg4ODg4ODg4ODg4ODg4ODg8Pqw7M0BM+n9I0oAAAAASUVORK5CYII=</Image>
+<!-- #00FFFF -->
+<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMoaoKANQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAYklEQVR42u3RQQ0AAAjEMMC/58MECZ9OwtqVlL4bCzAIAwZhwCAMGIQBgzBgEAYMwoBBGDAIAwZhwCAMGIQBgzBgEAYMwoBBGIQBgzBgEAYMwoBBGDAIAwZhwCAMGIQBg+5ar7sCZiri9VUAAAAASUVORK5CYII=</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngine1xLogo.xml b/browser/base/content/test/newtab/searchEngine1xLogo.xml
new file mode 100644
index 000000000..19ac03f48
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngine1xLogo.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngine1xLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1xlogo" rel="searchform"/>
+<!-- #FF0000 -->
+<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEwS9h64QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVRYw+3PAREAAAQEsKd/Z3I4W4PV5LyOg4ODg4ODg4ODg4ODg4ODg4PD78MC7cwBM02qquMAAAAASUVORK5CYII=</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngine2xLogo.xml b/browser/base/content/test/newtab/searchEngine2xLogo.xml
new file mode 100644
index 000000000..941bf040d
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngine2xLogo.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngine2xLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/2xlogo" rel="searchform"/>
+<!-- #0000FF -->
+<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMURe38sgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAYklEQVR42u3RQQ0AAAjEsAP/nsEECZ9OwlrJRN+1BRiEAYMwYBAGDMKAQRgwCAMGYcAgDBiEAYMwYBAGDMKAQRgwCAMGYcAgDMKAQRgwCAMGYcAgDBiEAYMwYBAGDMKAQXctkIQBZ/1YP3YAAAAASUVORK5CYII=</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngineFavicon.xml b/browser/base/content/test/newtab/searchEngineFavicon.xml
new file mode 100644
index 000000000..6f2a970f5
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineFavicon.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngineFavicon.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1xlogo" rel="searchform"/>
+<Image width="16" height="16">data:application/ico;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAA/wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAAAAAABAAAEwsAABMLAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngineNoLogo.xml b/browser/base/content/test/newtab/searchEngineNoLogo.xml
new file mode 100644
index 000000000..bbff6cf8f
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineNoLogo.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngineNoLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/nologo" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/plugins/.eslintrc.js b/browser/base/content/test/plugins/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/plugins/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/plugins/blockNoPlugins.xml b/browser/base/content/test/plugins/blockNoPlugins.xml
new file mode 100644
index 000000000..e4e191b37
--- /dev/null
+++ b/browser/base/content/test/plugins/blockNoPlugins.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310001">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginHard.xml b/browser/base/content/test/plugins/blockPluginHard.xml
new file mode 100644
index 000000000..24eb5bc6f
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginHard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginInfoURL.xml b/browser/base/content/test/plugins/blockPluginInfoURL.xml
new file mode 100644
index 000000000..c16808896
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginInfoURL.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ <infoURL>http://test.url.com/</infoURL>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml b/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml
new file mode 100644
index 000000000..bf8545afe
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml b/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml
new file mode 100644
index 000000000..5545162b1
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blocklist_proxy.js b/browser/base/content/test/plugins/blocklist_proxy.js
new file mode 100644
index 000000000..1a4ed4726
--- /dev/null
+++ b/browser/base/content/test/plugins/blocklist_proxy.js
@@ -0,0 +1,78 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cm = Components.manager;
+
+const kBlocklistServiceUUID = "{66354bc9-7ed1-4692-ae1d-8da97d6b205e}";
+const kBlocklistServiceContractID = "@mozilla.org/extensions/blocklist;1";
+const kBlocklistServiceFactory = Cm.getClassObject(Cc[kBlocklistServiceContractID], Ci.nsIFactory);
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+/*
+ * A lightweight blocklist proxy for the testing purposes.
+ */
+var BlocklistProxy = {
+ _uuid: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsIBlocklistService,
+ Ci.nsITimerCallback]),
+
+ init: function() {
+ if (!this._uuid) {
+ this._uuid =
+ Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator)
+ .generateUUID();
+ Cm.nsIComponentRegistrar.registerFactory(this._uuid, "",
+ "@mozilla.org/extensions/blocklist;1",
+ this);
+ }
+ },
+
+ uninit: function() {
+ if (this._uuid) {
+ Cm.nsIComponentRegistrar.unregisterFactory(this._uuid, this);
+ Cm.nsIComponentRegistrar.registerFactory(Components.ID(kBlocklistServiceUUID),
+ "Blocklist Service",
+ "@mozilla.org/extensions/blocklist;1",
+ kBlocklistServiceFactory);
+ this._uuid = null;
+ }
+ },
+
+ notify: function (aTimer) {
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ },
+
+ isAddonBlocklisted: function (aAddon, aAppVersion, aToolkitVersion) {
+ return false;
+ },
+
+ getAddonBlocklistState: function (aAddon, aAppVersion, aToolkitVersion) {
+ return 0; // STATE_NOT_BLOCKED
+ },
+
+ getPluginBlocklistState: function (aPluginTag, aAppVersion, aToolkitVersion) {
+ return 0; // STATE_NOT_BLOCKED
+ },
+
+ getAddonBlocklistURL: function (aAddon, aAppVersion, aToolkitVersion) {
+ return "";
+ },
+
+ getPluginBlocklistURL: function (aPluginTag) {
+ return "";
+ },
+
+ getPluginInfoURL: function (aPluginTag) {
+ return "";
+ },
+}
+
+BlocklistProxy.init();
+addEventListener("unload", () => {
+ BlocklistProxy.uninit();
+});
diff --git a/browser/base/content/test/plugins/browser.ini b/browser/base/content/test/plugins/browser.ini
new file mode 100644
index 000000000..cfc1f769c
--- /dev/null
+++ b/browser/base/content/test/plugins/browser.ini
@@ -0,0 +1,78 @@
+[DEFAULT]
+support-files =
+ blocklist_proxy.js
+ blockNoPlugins.xml
+ blockPluginHard.xml
+ blockPluginInfoURL.xml
+ blockPluginVulnerableNoUpdate.xml
+ blockPluginVulnerableUpdatable.xml
+ browser_clearplugindata.html
+ browser_clearplugindata_noage.html
+ head.js
+ plugin_add_dynamically.html
+ plugin_alternate_content.html
+ plugin_big.html
+ plugin_both.html
+ plugin_both2.html
+ plugin_bug744745.html
+ plugin_bug749455.html
+ plugin_bug787619.html
+ plugin_bug797677.html
+ plugin_bug820497.html
+ plugin_clickToPlayAllow.html
+ plugin_clickToPlayDeny.html
+ plugin_data_url.html
+ plugin_hidden_to_visible.html
+ plugin_iframe.html
+ plugin_outsideScrollArea.html
+ plugin_overlayed.html
+ plugin_positioned.html
+ plugin_small.html
+ plugin_small_2.html
+ plugin_syncRemoved.html
+ plugin_test.html
+ plugin_test2.html
+ plugin_test3.html
+ plugin_two_types.html
+ plugin_unknown.html
+ plugin_crashCommentAndURL.html
+ plugin_zoom.html
+
+[browser_bug743421.js]
+[browser_bug744745.js]
+[browser_bug787619.js]
+[browser_bug797677.js]
+[browser_bug812562.js]
+[browser_bug818118.js]
+[browser_bug820497.js]
+[browser_clearplugindata.js]
+[browser_CTP_context_menu.js]
+skip-if = toolkit == "gtk2" || toolkit == "gtk3" # fails intermittently on Linux (bug 909342)
+[browser_CTP_crashreporting.js]
+skip-if = !crashreporter
+[browser_CTP_data_urls.js]
+[browser_CTP_drag_drop.js]
+[browser_CTP_hide_overlay.js]
+[browser_CTP_iframe.js]
+[browser_CTP_multi_allow.js]
+[browser_CTP_nonplugins.js]
+[browser_CTP_notificationBar.js]
+[browser_CTP_outsideScrollArea.js]
+[browser_CTP_remove_navigate.js]
+[browser_CTP_resize.js]
+[browser_CTP_zoom.js]
+[browser_blocking.js]
+[browser_plugins_added_dynamically.js]
+[browser_pluginnotification.js]
+[browser_plugin_reloading.js]
+[browser_blocklist_content.js]
+skip-if = !e10s
+[browser_globalplugin_crashinfobar.js]
+skip-if = !crashreporter
+[browser_pluginCrashCommentAndURL.js]
+skip-if = !crashreporter
+[browser_pageInfo_plugins.js]
+[browser_pluginCrashReportNonDeterminism.js]
+skip-if = !crashreporter || os == 'linux' # Bug 1152811
+[browser_private_clicktoplay.js]
+
diff --git a/browser/base/content/test/plugins/browser_CTP_context_menu.js b/browser/base/content/test/plugins/browser_CTP_context_menu.js
new file mode 100644
index 000000000..03f3e18ef
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_context_menu.js
@@ -0,0 +1,69 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+// Test that the activate action in content menus for CTP plugins works
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ let bindingPromise = waitForEvent(gBrowser.selectedBrowser, "PluginBindingAttached", null, true, true);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+ yield bindingPromise;
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(!pluginInfo.activated, "plugin should not be activated");
+
+ // Display a context menu on the test plugin so we can test
+ // activation menu options.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("contextmenu", left, top, 2, 1, 0);
+ });
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Should have a click-to-play notification");
+ ok(popupNotification.dismissed, "notification should be dismissed");
+
+ // fixes a occasional test timeout on win7 opt
+ yield promiseForCondition(() => document.getElementById("context-ctp-play"));
+
+ let actMenuItem = document.getElementById("context-ctp-play");
+ ok(actMenuItem, "Should have a context menu entry for activating the plugin");
+
+ // Activate the plugin via the context menu
+ EventUtils.synthesizeMouseAtCenter(actMenuItem, {});
+
+ yield promiseForCondition(() => !PopupNotifications.panel.dismissed && PopupNotifications.panel.firstChild);
+
+ // Activate the plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should not be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_crashreporting.js b/browser/base/content/test/plugins/browser_CTP_crashreporting.js
new file mode 100644
index 000000000..bb52d5704
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_crashreporting.js
@@ -0,0 +1,233 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+const PLUGIN_PAGE = gTestRoot + "plugin_big.html";
+const PLUGIN_SMALL_PAGE = gTestRoot + "plugin_small.html";
+
+/**
+ * Takes an nsIPropertyBag and converts it into a JavaScript Object. It
+ * will also convert any nsIPropertyBag's within the nsIPropertyBag
+ * recursively.
+ *
+ * @param aBag
+ * The nsIPropertyBag to convert.
+ * @return Object
+ * Keyed on the names of the nsIProperty's within the nsIPropertyBag,
+ * and mapping to their values.
+ */
+function convertPropertyBag(aBag) {
+ let result = {};
+ let enumerator = aBag.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let { name, value } = enumerator.getNext().QueryInterface(Ci.nsIProperty);
+ if (value instanceof Ci.nsIPropertyBag) {
+ value = convertPropertyBag(value);
+ }
+ result[name] = value;
+ }
+ return result;
+}
+
+add_task(function* setup() {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
+ // crash reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverURL = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ registerCleanupFunction(function cleanUp() {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverURL);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ window.focus();
+ });
+});
+
+/**
+ * Test that plugin crash submissions still work properly after
+ * click-to-play activation.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PLUGIN_PAGE,
+ }, function* (browser) {
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(browser);
+
+ let pluginInfo = yield promiseForPluginInfo("test", browser);
+ ok(!pluginInfo.activated, "Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
+ yield promiseForNotificationShown(notification, browser);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // Prepare a crash report topic observer that only returns when
+ // the crash report has been successfully sent.
+ let crashReportChecker = (subject, data) => {
+ return (data == "success");
+ };
+ let crashReportPromise = TestUtils.topicObserved("crash-report-status",
+ crashReportChecker);
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let plugin = content.document.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ yield ContentTaskUtils.waitForCondition(() => {
+ return plugin.activated;
+ }, "Waited too long for plugin to activate.");
+
+ try {
+ Components.utils.waiveXrays(plugin).crash();
+ } catch (e) {
+ }
+
+ let doc = plugin.ownerDocument;
+
+ let getUI = (anonid) => {
+ return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
+ };
+
+ // Now wait until the plugin crash report UI shows itself, which is
+ // asynchronous.
+ let statusDiv;
+
+ yield ContentTaskUtils.waitForCondition(() => {
+ statusDiv = getUI("submitStatus");
+ return statusDiv.getAttribute("status") == "please";
+ }, "Waited too long for plugin to show crash report UI");
+
+ // Make sure the UI matches our expectations...
+ let style = content.getComputedStyle(getUI("pleaseSubmit"));
+ if (style.display != "block") {
+ throw new Error(`Submission UI visibility is not correct. ` +
+ `Expected block style, got ${style.display}.`);
+ }
+
+ // Fill the crash report in with some test values that we'll test for in
+ // the parent.
+ getUI("submitComment").value = "a test comment";
+ let optIn = getUI("submitURLOptIn");
+ if (!optIn.checked) {
+ throw new Error("URL opt-in should default to true.");
+ }
+
+ // Submit the report.
+ optIn.click();
+ getUI("submitButton").click();
+
+ // And wait for the parent to say that the crash report was submitted
+ // successfully.
+ yield ContentTaskUtils.waitForCondition(() => {
+ return statusDiv.getAttribute("status") == "success";
+ }, "Timed out waiting for plugin binding to be in success state");
+ });
+
+ let [subject, ] = yield crashReportPromise;
+
+ ok(subject instanceof Ci.nsIPropertyBag,
+ "The crash report subject should be an nsIPropertyBag.");
+
+ let crashData = convertPropertyBag(subject);
+ ok(crashData.serverCrashID, "Should have a serverCrashID set.");
+
+ // Remove the submitted report file after ensuring it exists.
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(crashData.serverCrashID + ".txt");
+ ok(file.exists(), "Submitted report file should exist");
+ file.remove(false);
+
+ ok(crashData.extra, "Extra data should exist");
+ is(crashData.extra.PluginUserComment, "a test comment",
+ "Comment in extra data should match comment in textbox");
+
+ is(crashData.extra.PluginContentURL, undefined,
+ "URL should be absent from extra data when opt-in not checked");
+ });
+});
+
+/**
+ * Test that plugin crash submissions still work properly after
+ * click-to-play with the notification bar.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PLUGIN_SMALL_PAGE,
+ }, function* (browser) {
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(browser);
+
+ let pluginInfo = yield promiseForPluginInfo("test", browser);
+ ok(pluginInfo.activated, "Plugin should be activated from previous test");
+
+ // Prepare a crash report topic observer that only returns when
+ // the crash report has been successfully sent.
+ let crashReportChecker = (subject, data) => {
+ return (data == "success");
+ };
+ let crashReportPromise = TestUtils.topicObserved("crash-report-status",
+ crashReportChecker);
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let plugin = content.document.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ yield ContentTaskUtils.waitForCondition(() => {
+ return plugin.activated;
+ }, "Waited too long for plugin to activate.");
+
+ try {
+ Components.utils.waiveXrays(plugin).crash();
+ } catch (e) {}
+ });
+
+ // Wait for the notification bar to be displayed.
+ let notification = yield waitForNotificationBar("plugin-crashed", browser);
+
+ // Then click the button to submit the crash report.
+ let buttons = notification.querySelectorAll(".notification-button");
+ is(buttons.length, 2, "Should have two buttons.");
+
+ // The "Submit Crash Report" button should be the second one.
+ let submitButton = buttons[1];
+ submitButton.click();
+
+ let [subject, ] = yield crashReportPromise;
+
+ ok(subject instanceof Ci.nsIPropertyBag,
+ "The crash report subject should be an nsIPropertyBag.");
+
+ let crashData = convertPropertyBag(subject);
+ ok(crashData.serverCrashID, "Should have a serverCrashID set.");
+
+ // Remove the submitted report file after ensuring it exists.
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(crashData.serverCrashID + ".txt");
+ ok(file.exists(), "Submitted report file should exist");
+ file.remove(false);
+
+ is(crashData.extra.PluginContentURL, undefined,
+ "URL should be absent from extra data when opt-in not checked");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_data_urls.js b/browser/base/content/test/plugins/browser_CTP_data_urls.js
new file mode 100644
index 000000000..0f4747b1e
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_data_urls.js
@@ -0,0 +1,255 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
+
+// Test that the click-to-play doorhanger still works when navigating to data URLs
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1a, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
+
+ let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ // navigate forward to a page with 'test' in it
+ content.document.getElementById("data-link-1").click();
+ });
+ yield loadPromise;
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1b, Should have a click-to-play notification");
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 1b, plugin should not be activated");
+
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // Simulate clicking the "Allow Always" button.
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 1b, plugin should be activated");
+});
+
+// Test that the click-to-play notification doesn't break when navigating
+// to data URLs with multiple plugins.
+add_task(function* () {
+ // We click activated above
+ clearAllPluginPermissions();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 2a, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 2a, plugin should not be activated");
+
+ let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ // navigate forward to a page with 'test1' & 'test2' in it
+ content.document.getElementById("data-link-2").click();
+ });
+ yield loadPromise;
+
+ // Work around for delayed PluginBindingAttached
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test1").clientTop;
+ content.document.getElementById("test2").clientTop;
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test1");
+ ok(!pluginInfo.activated, "Test 2a, test1 should not be activated");
+ pluginInfo = yield promiseForPluginInfo("test2");
+ ok(!pluginInfo.activated, "Test 2a, test2 should not be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 2b, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ // Simulate choosing "Allow now" for the test plugin
+ is(notification.options.pluginData.size, 2, "Test 2b, Should have two types of plugin in the notification");
+
+ let centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 2b, found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 2b, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 2b, found center item for the Test plugin");
+
+ // "click" the button to activate the Test plugin
+ centerItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test1");
+ ok(pluginInfo.activated, "Test 2b, plugin should be activated");
+});
+
+add_task(function* () {
+ // We click activated above
+ clearAllPluginPermissions();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+});
+
+// Test that when navigating to a data url, the plugin permission is inherited
+add_task(function* () {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 3a, Should have a click-to-play notification");
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 3a, plugin should not be activated");
+
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // Simulate clicking the "Allow Always" button.
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 3a, plugin should be activated");
+
+ let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ // navigate forward to a page with 'test' in it
+ content.document.getElementById("data-link-1").click();
+ });
+ yield loadPromise;
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 3b, plugin should be activated");
+
+ clearAllPluginPermissions();
+});
+
+// Test that the click-to-play doorhanger still works
+// when directly navigating to data URLs.
+// Fails, bug XXX. Plugins plus a data url don't fire a load event.
+/*
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab,
+ "data:text/html,Hi!<embed id='test' style='width:200px; height:200px' type='application/x-test'/>");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 4a, Should have a click-to-play notification");
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 4a, plugin should not be activated");
+
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // Simulate clicking the "Allow Always" button.
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 4a, plugin should be activated");
+});
+*/
diff --git a/browser/base/content/test/plugins/browser_CTP_drag_drop.js b/browser/base/content/test/plugins/browser_CTP_drag_drop.js
new file mode 100644
index 000000000..7c9858e27
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_drag_drop.js
@@ -0,0 +1,96 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gNewWindow = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gNewWindow.close();
+ gNewWindow = null;
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+add_task(function* () {
+ gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+
+ // XXX technically can't load fire before we get this call???
+ yield waitForEvent(gNewWindow, "load", null, true);
+
+ yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+ ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, gNewWindow.gBrowser.selectedTab);
+
+ yield promisePopupNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab again");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+});
+
+add_task(function* () {
+ yield promisePopupNotification("click-to-play-plugins");
+
+ gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+
+ yield promiseWaitForFocus(gNewWindow);
+
+ yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+});
+
+add_task(function* () {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+ ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+
+ let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser);
+ ok(!pluginInfo.activated, "plugin should not be activated");
+
+ yield ContentTask.spawn(gNewWindow.gBrowser.selectedBrowser, {}, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+});
+
+add_task(function* () {
+ // Click the activate button on doorhanger to make sure it works
+ gNewWindow.PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_hide_overlay.js b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js
new file mode 100644
index 000000000..5fab7f6ed
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js
@@ -0,0 +1,88 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ // Tests that the overlay can be hidden for plugins using the close icon.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon")
+ let bounds = closeIcon.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+
+ Assert.ok(!overlay.classList.contains("visible"), "overlay should be hidden.");
+ });
+});
+
+// Test that the overlay cannot be interacted with after the user closes the overlay
+add_task(function* () {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon")
+ let closeIconBounds = closeIcon.getBoundingClientRect();
+ let overlayBounds = overlay.getBoundingClientRect();
+ let overlayLeft = (overlayBounds.left + overlayBounds.right) / 2;
+ let overlayTop = (overlayBounds.left + overlayBounds.right) / 2 ;
+ let closeIconLeft = (closeIconBounds.left + closeIconBounds.right) / 2;
+ let closeIconTop = (closeIconBounds.top + closeIconBounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ // Simulate clicking on the close icon.
+ utils.sendMouseEvent("mousedown", closeIconLeft, closeIconTop, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", closeIconLeft, closeIconTop, 0, 1, 0, false, 0, 0);
+
+ // Simulate clicking on the overlay.
+ utils.sendMouseEvent("mousedown", overlayLeft, overlayTop, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", overlayLeft, overlayTop, 0, 1, 0, false, 0, 0);
+
+ Assert.ok(overlay.hasAttribute("dismissed") && !overlay.classList.contains("visible"),
+ "Overlay should be hidden");
+ });
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+ ok(notification.dismissed, "No notification should be shown");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_iframe.js b/browser/base/content/test/plugins/browser_CTP_iframe.js
new file mode 100644
index 000000000..58565559f
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_iframe.js
@@ -0,0 +1,48 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_iframe.html");
+
+ // Tests that the overlays are visible and actionable if the plugin is in an iframe.
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let frame = content.document.getElementById("frame");
+ let doc = frame.contentDocument;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(plugin && overlay.classList.contains("visible"),
+ "Test 1, Plugin overlay should exist, not be hidden");
+
+ let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon");
+ let bounds = closeIcon.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = doc.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ Assert.ok(!overlay.classList.contains("visible"),
+ "Test 1, Plugin overlay should exist, be hidden");
+ });
+});
+
diff --git a/browser/base/content/test/plugins/browser_CTP_multi_allow.js b/browser/base/content/test/plugins/browser_CTP_multi_allow.js
new file mode 100644
index 000000000..7bc6aaabf
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_multi_allow.js
@@ -0,0 +1,99 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ // Test that the click-to-play doorhanger for multiple plugins shows the correct
+ // state when re-opening without reloads or navigation.
+
+ let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(!pluginInfo.activated, "plugin should be activated");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(notification, "Test 1a, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 2,
+ "Test 1a, Should have two types of plugin in the notification");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ is(PopupNotifications.panel.firstChild.childNodes.length, 2, "have child nodes");
+
+ let pluginItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 1a, all plugins should start out blocked");
+ if (item.action.pluginName == "Test") {
+ pluginItem = item;
+ }
+ }
+
+ // Choose "Allow now" for the test plugin
+ pluginItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(notification, "Test 1b, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ pluginItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action.pluginName == "Test") {
+ is(item.value, "allownow", "Test 1b, Test plugin should now be set to 'Allow now'");
+ } else {
+ is(item.value, "block", "Test 1b, Second Test plugin should still be blocked");
+ pluginItem = item;
+ }
+ }
+
+ // Choose "Allow and remember" for the Second Test plugin
+ pluginItem.value = "allowalways";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("secondtestA", gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(notification, "Test 1c, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action.pluginName == "Test") {
+ is(item.value, "allownow", "Test 1c, Test plugin should be set to 'Allow now'");
+ } else {
+ is(item.value, "allowalways", "Test 1c, Second Test plugin should be set to 'Allow always'");
+ }
+ }
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_nonplugins.js b/browser/base/content/test/plugins/browser_CTP_nonplugins.js
new file mode 100644
index 000000000..cdef44d9d
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_nonplugins.js
@@ -0,0 +1,58 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_DISABLED, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ // Test that the click-to-play notification is not shown for non-plugin object elements
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+ let pluginRemovedPromise = waitForEvent(gBrowser.selectedBrowser, "PluginRemoved", null, true, true);
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("secondtestA");
+ plugin.parentNode.removeChild(plugin);
+ plugin = content.document.getElementById("secondtestB");
+ plugin.parentNode.removeChild(plugin);
+
+ let image = content.document.createElement("object");
+ image.type = "image/png";
+ image.data = "moz.png";
+ content.document.body.appendChild(image);
+ });
+ yield pluginRemovedPromise;
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 2, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.parentNode.removeChild(plugin);
+ });
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 3, Should still have a click-to-play notification");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_notificationBar.js b/browser/base/content/test/plugins/browser_CTP_notificationBar.js
new file mode 100644
index 000000000..3c7bd911c
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_notificationBar.js
@@ -0,0 +1,151 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ // Expecting a notification bar for hidden plugins
+ yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+});
+
+add_task(function* () {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null);
+});
+
+add_task(function* () {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Expecting a plugin notification bar when plugins are overlaid.
+ yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 3b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 3b, overlay should be hidden.");
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_positioned.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Expecting a plugin notification bar when plugins are overlaid offscreen.
+ yield promisePopupNotification("click-to-play-plugins");
+ yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 4b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 4b, overlay should be hidden.");
+ });
+});
+
+// Test that the notification bar is getting dismissed when directly activating plugins
+// via the doorhanger.
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Expecting a plugin notification bar when plugins are overlaid offscreen.
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 6, Plugin should be click-to-play");
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 6, Should have a click-to-play notification");
+
+ // simulate "always allow"
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 7, plugin should be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
new file mode 100644
index 000000000..ccb4d11d7
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
@@ -0,0 +1,120 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+});
+
+// Test that the click-to-play overlay is not hidden for elements
+// partially or fully outside the viewport.
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let p = doc.createElement('embed');
+
+ p.setAttribute('id', 'test');
+ p.setAttribute('type', 'application/x-test');
+ p.style.left = "0";
+ p.style.bottom = "200px";
+
+ doc.getElementById('container').appendChild(p);
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let doc = content.document;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 2, overlay should be visible.");
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let p = doc.createElement('embed');
+
+ p.setAttribute('id', 'test');
+ p.setAttribute('type', 'application/x-test');
+ p.style.left = "0";
+ p.style.bottom = "-410px";
+
+ doc.getElementById('container').appendChild(p);
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let doc = content.document;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 3, overlay should be visible.");
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let p = doc.createElement('embed');
+
+ p.setAttribute('id', 'test');
+ p.setAttribute('type', 'application/x-test');
+ p.style.left = "-600px";
+ p.style.bottom = "0";
+
+ doc.getElementById('container').appendChild(p);
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let doc = content.document;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 4, overlay should be hidden.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_remove_navigate.js b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
new file mode 100644
index 000000000..8ee1c5b5a
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
@@ -0,0 +1,79 @@
+const gTestRoot = getRootDirectory(gTestPath);
+const gHttpTestRoot = gTestRoot.replace("chrome://mochitests/content/",
+ "http://127.0.0.1:8888/");
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page, that we don't show the hidden plugin
+ * notification bar on the new page.
+ */
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ // Load up a page with a plugin...
+ let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html");
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+ yield notificationPromise;
+
+ // Trigger the PluginRemoved event to be fired, and then immediately
+ // browse to a new page.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.remove();
+ });
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "about:mozilla");
+
+ // There should be no hidden plugin notification bar at about:mozilla.
+ let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
+ is(notificationBox.getNotificationWithValue("plugin-hidden"), null,
+ "Expected no notification box");
+});
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page with a plugin, that we show the right notification
+ * for the new page.
+ */
+add_task(function* () {
+ // Load up a page with a plugin...
+ let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html");
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+ yield notificationPromise;
+
+ // Trigger the PluginRemoved event to be fired, and then immediately
+ // browse to a new page.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.remove();
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small_2.html");
+ let notification = yield waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+ ok(notification, "There should be a notification shown for the new page.");
+ // Ensure that the notification is showing information about
+ // the x-second-test plugin.
+ let label = notification.label;
+ ok(label.includes("Second Test"), "Should mention the second plugin");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_resize.js b/browser/base/content/test/plugins/browser_CTP_resize.js
new file mode 100644
index 000000000..9b2a2cd82
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_resize.js
@@ -0,0 +1,130 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+
+ yield promiseTabLoadEvent(newTab, gTestRoot + "plugin_small.html"); // 10x10 plugin
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+// Test that the overlay is hidden for "small" plugin elements and is shown
+// once they are resized to a size that can hold the overlay
+add_task(function* () {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 2, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 2, overlay should be hidden.");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.width = "300px";
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 3, overlay should be hidden.");
+ });
+});
+
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.height = "300px";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test").clientTop;
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 4, overlay should be visible.");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.width = "10px";
+ plugin.style.height = "10px";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test").clientTop;
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 5, overlay should be hidden.");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.height = "300px";
+ plugin.style.width = "300px";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test").clientTop;
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 6, overlay should be visible.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_zoom.js b/browser/base/content/test/plugins/browser_CTP_zoom.js
new file mode 100644
index 000000000..8b353232d
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_zoom.js
@@ -0,0 +1,62 @@
+"use strict";
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ FullZoom.reset(); // must be called before closing the tab we zoomed!
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+// Enlarges the zoom level 4 times and tests that the overlay is
+// visible after each enlargement.
+add_task(function* () {
+ for (let count = 0; count < 4; count++) {
+
+ FullZoom.enlarge();
+
+ // Reload the page
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html");
+ yield promiseUpdatePluginBindings(gTestBrowser);
+ yield ContentTask.spawn(gTestBrowser, { count }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Overlay should be visible for zoom change count " + args.count);
+ });
+ }
+});
+
+
diff --git a/browser/base/content/test/plugins/browser_blocking.js b/browser/base/content/test/plugins/browser_blocking.js
new file mode 100644
index 000000000..334ed9f2e
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_blocking.js
@@ -0,0 +1,349 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+function updateAllTestPlugins(aState) {
+ setTestPluginEnabledState(aState, "Test Plug-in");
+ setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ // Prime the content process
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+
+ // Make sure the blocklist service(s) are running
+ Components.classes["@mozilla.org/extensions/blocklist;1"]
+ .getService(Components.interfaces.nsIBlocklistService);
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+});
+
+add_task(function* () {
+ // enable hard blocklisting for the next test
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
+
+ yield promiseForNotificationShown(notification);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+ is(notification.options.pluginData.size, 1, "Test 5: Only the blocked plugin should be present in the notification");
+ ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests a vulnerable, updatable plugin
+
+add_task(function* () {
+ // enable hard blocklisting of test
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+ "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!pluginInfo.activated, "Test 18a, Plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 18a, Plugin overlay should exist, not be hidden");
+
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ Assert.ok(updateLink.style.visibility != "hidden",
+ "Test 18a, Plugin should have an update link");
+ });
+
+ let promise = waitForEvent(gBrowser.tabContainer, "TabOpen", null, true);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ let bounds = updateLink.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ promise = waitForEvent(gBrowser.tabContainer, "TabClose", null, true);
+ gBrowser.removeCurrentTab();
+ yield promise;
+});
+
+add_task(function* () {
+ // clicking the update link should not activate the plugin
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+ "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!pluginInfo.activated, "Test 18b, Plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 18b, Plugin overlay should exist, not be hidden");
+ });
+});
+
+// Tests a vulnerable plugin with no update
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableNoUpdate.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 18c, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE,
+ "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+ ok(!pluginInfo.activated, "Test 18c, Plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 18c, Plugin overlay should exist, not be hidden");
+
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ Assert.ok(updateLink && updateLink.style.display != "block",
+ "Test 18c, Plugin should not have an update link");
+ });
+
+ // check that click "Always allow" works with blocked plugins
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE,
+ "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+ ok(pluginInfo.activated, "Test 18c, Plugin should be activated");
+ let enabledState = getTestPluginEnabledState();
+ ok(enabledState, "Test 18c, Plugin enabled state should be STATE_CLICKTOPLAY");
+});
+
+// continue testing "Always allow", make sure it sticks.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 18d, Waited too long for plugin to activate");
+
+ clearAllPluginPermissions();
+});
+
+// clicking the in-content overlay of a vulnerable plugin should bring
+// up the notification and not directly activate the plugin
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 18f, Should have a click-to-play notification");
+ ok(notification.dismissed, "Test 18f, notification should start dismissed");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18f, Waited too long for plugin to activate");
+
+ var oldEventCallback = notification.options.eventCallback;
+ let promise = promiseForCondition(() => oldEventCallback == null);
+ notification.options.eventCallback = function() {
+ if (oldEventCallback) {
+ oldEventCallback();
+ }
+ oldEventCallback = null;
+ };
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ ok(notification, "Test 18g, Should have a click-to-play notification");
+ ok(!notification.dismissed, "Test 18g, notification should be open");
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+});
+
+// Test that "always allow"-ing a plugin will not allow it when it becomes
+// blocklisted.
+add_task(function* () {
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 24a, Should have a click-to-play notification");
+
+ // Plugin should start as CTP
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 24a, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ ok(!pluginInfo.activated, "Test 24a, Plugin should not be active.");
+
+ // simulate "always allow"
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 24a, Plugin should be active.");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+});
+
+// the plugin is now blocklisted, so it should not automatically load
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 24b, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+ "Test 24b, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!pluginInfo.activated, "Test 24b, Plugin should not be active.");
+
+ // simulate "always allow"
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 24b, Plugin should be active.");
+
+ clearAllPluginPermissions();
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Plugin sync removal test. Note this test produces a notification drop down since
+// the plugin we add has zero dims.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html");
+
+ // Maybe there some better trick here, we need to wait for the page load, then
+ // wait for the js to execute in the page.
+ yield waitForMs(500);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
+ ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+});
+
+// Tests a page with a blocked plugin in it and make sure the infoURL property
+// the blocklist file gets used.
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+ // Since the plugin notification is dismissed by default, reshow it.
+ yield promiseForNotificationShown(notification);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
+ "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(!objLoadingContent.activated, "Plugin should not be activated.");
+ });
+
+ const testUrl = "http://test.url.com/";
+
+ let firstPanelChild = PopupNotifications.panel.firstChild;
+ let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid",
+ "click-to-play-plugins-notification-link");
+ is(infoLink.href, testUrl,
+ "Test 26, the notification URL needs to match the infoURL from the blocklist file.");
+});
+
diff --git a/browser/base/content/test/plugins/browser_blocklist_content.js b/browser/base/content/test/plugins/browser_blocklist_content.js
new file mode 100644
index 000000000..bf4e159bc
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_blocklist_content.js
@@ -0,0 +1,104 @@
+var gTestBrowser = null;
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gChromeRoot = getRootDirectory(gTestPath);
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ Assert.ok(test.activated, "task 1a: test plugin should be activated!");
+ });
+});
+
+// Load a fresh page, load a new plugin blocklist, then load the same page again.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ ok(!test.activated, "task 2a: test plugin shouldn't activate!");
+ });
+});
+
+// Unload the block list and lets do this again, only this time lets
+// hack around in the content blocklist service maliciously.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ // Hack the planet! Load our blocklist shim, so we can mess with blocklist
+ // return results in the content process. Active until we close our tab.
+ let mm = gTestBrowser.messageManager;
+ info("test 3a: loading " + gChromeRoot + "blocklist_proxy.js" + "\n");
+ mm.loadFrameScript(gChromeRoot + "blocklist_proxy.js", true);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ Assert.ok(test.activated, "task 3a: test plugin should be activated!");
+ });
+});
+
+// Load a fresh page, load a new plugin blocklist, then load the same page again.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ Assert.ok(!test.activated, "task 4a: test plugin shouldn't activate!");
+ });
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
diff --git a/browser/base/content/test/plugins/browser_bug743421.js b/browser/base/content/test/plugins/browser_bug743421.js
new file mode 100644
index 000000000..966e7b012
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug743421.js
@@ -0,0 +1,119 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+add_task(function* () {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests that navigation within the page and the window.history API doesn't break click-to-play state.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 1a, Should not have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[0];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+ });
+
+ // Click the activate button on doorhanger to make sure it works
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[0];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+ });
+});
+
+add_task(function* () {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 1c, Should still have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ let plugin = content.document.getElementsByTagName("embed")[1];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated,
+ "Test 1c, Newly inserted plugin in activated page should be activated");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[1];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1d, Plugin should be activated");
+
+ let promise = ContentTaskUtils.waitForEvent(content, "hashchange");
+ content.location += "#anchorNavigation";
+ yield promise;
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ let plugin = content.document.getElementsByTagName("embed")[2];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1e, Plugin should be activated");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[2];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1f, Plugin should be activated");
+
+ content.history.replaceState({}, "", "replacedState");
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ plugin = content.document.getElementsByTagName("embed")[3];
+ objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1g, Plugin should be activated");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug744745.js b/browser/base/content/test/plugins/browser_bug744745.js
new file mode 100644
index 000000000..c9f552a4e
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug744745.js
@@ -0,0 +1,50 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gNumPluginBindingsAttached = 0;
+
+function pluginBindingAttached() {
+ gNumPluginBindingsAttached++;
+ if (gNumPluginBindingsAttached != 1) {
+ ok(false, "if we've gotten here, something is quite wrong");
+ }
+}
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+
+ let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug744745.html");
+
+ yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ if (!plugin) {
+ Assert.ok(false, "plugin element not available.");
+ return;
+ }
+ // We can't use MochiKit's routine
+ let style = content.getComputedStyle(plugin);
+ Assert.ok(("opacity" in style) && style.opacity == 1, "plugin style properly configured.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug787619.js b/browser/base/content/test/plugins/browser_bug787619.js
new file mode 100644
index 000000000..bfd52258c
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug787619.js
@@ -0,0 +1,65 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gWrapperClickCount = 0;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug787619.html");
+
+ // Due to layout being async, "PluginBindAttached" may trigger later.
+ // This forces a layout flush, thus triggering it, and schedules the
+ // test so it is definitely executed afterwards.
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("plugin");
+ ok(!pluginInfo.activated, "1a plugin should not be activated");
+
+ // click the overlay to prompt
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("plugin");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("plugin");
+ ok(!pluginInfo.activated, "1b plugin should not be activated");
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("plugin");
+ ok(pluginInfo.activated, "plugin should be activated");
+
+ is(gWrapperClickCount, 0, 'wrapper should not have received any clicks');
+});
diff --git a/browser/base/content/test/plugins/browser_bug797677.js b/browser/base/content/test/plugins/browser_bug797677.js
new file mode 100644
index 000000000..1ae9f5047
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug797677.js
@@ -0,0 +1,43 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gConsoleErrors = 0;
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ consoleService.unregisterListener(errorListener);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ let consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+ let errorListener = {
+ observe: function(aMessage) {
+ if (aMessage.message.includes("NS_ERROR_FAILURE"))
+ gConsoleErrors++;
+ }
+ };
+ consoleService.registerListener(errorListener);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug797677.html");
+
+ let pluginInfo = yield promiseForPluginInfo("plugin");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "plugin should not have been found.");
+
+ // simple cpows
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ let plugin = content.document.getElementById("plugin");
+ ok(plugin, "plugin should be in the page");
+ });
+ is(gConsoleErrors, 0, "should have no console errors");
+});
diff --git a/browser/base/content/test/plugins/browser_bug812562.js b/browser/base/content/test/plugins/browser_bug812562.js
new file mode 100644
index 000000000..be7b00b22
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug812562.js
@@ -0,0 +1,80 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+});
+
+// Tests that the going back will reshow the notification for click-to-play
+// blocklisted plugins
+add_task(function* () {
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "test part 1: Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ Assert.ok(!!content.document.getElementById("test"),
+ "test part 1: plugin should not be activated");
+ });
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+});
+
+add_task(function* () {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "test part 2: Should not have a click-to-play notification");
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ Assert.ok(!content.document.getElementById("test"),
+ "test part 2: plugin should not be activated");
+ });
+
+ let obsPromise = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+ let overlayPromise = promisePopupNotification("click-to-play-plugins");
+ gTestBrowser.goBack();
+ yield obsPromise;
+ yield overlayPromise;
+});
+
+add_task(function* () {
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "test part 3: Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ Assert.ok(!!content.document.getElementById("test"),
+ "test part 3: plugin should not be activated");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug818118.js b/browser/base/content/test/plugins/browser_bug818118.js
new file mode 100644
index 000000000..9dd6e22e7
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug818118.js
@@ -0,0 +1,40 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_both.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin should be click to play");
+ ok(!pluginInfo.activated, "plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, () => {
+ let unknown = content.document.getElementById("unknown");
+ ok(unknown, "should have unknown plugin in page");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug820497.js b/browser/base/content/test/plugins/browser_bug820497.js
new file mode 100644
index 000000000..b2e0f5268
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug820497.js
@@ -0,0 +1,71 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gNumPluginBindingsAttached = 0;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ gTestBrowser.addEventListener("PluginBindingAttached", function () { gNumPluginBindingsAttached++ }, true, true);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug820497.html");
+
+ yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; });
+
+ yield ContentTask.spawn(gTestBrowser, null, () => {
+ // Note we add the second plugin in the code farther down, so there's
+ // no way we got here with anything but one plugin loaded.
+ let doc = content.document;
+ let testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ let secondtestplugin = doc.getElementById("secondtest");
+ ok(!secondtestplugin, "should not yet have second test plugin");
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ XPCNativeWrapper.unwrap(content).addSecondPlugin();
+ });
+
+ yield promiseForCondition(function () { return gNumPluginBindingsAttached == 2; });
+
+ yield ContentTask.spawn(gTestBrowser, null, () => {
+ let doc = content.document;
+ let testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ let secondtestplugin = doc.getElementById("secondtest");
+ ok(secondtestplugin, "should have second test plugin");
+ });
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ ok(notification, "should have popup notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 2, "aited too long for 2 types of plugins in popup notification");
+});
diff --git a/browser/base/content/test/plugins/browser_clearplugindata.html b/browser/base/content/test/plugins/browser_clearplugindata.html
new file mode 100644
index 000000000..243350ba4
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_clearplugindata.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data sanitize test</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ // Make sure clearing by timerange is supported.
+ var p = document.getElementById("plugin1");
+ p.setSitesWithDataCapabilities(true);
+
+ p.setSitesWithData(
+ "foo.com:0:5," +
+ "bar.com:0:100," +
+ "baz.com:1:5," +
+ "qux.com:1:100"
+ );
+ }
+ </script>
+ </head>
+
+ <body onload="testSteps();"></body>
+
+</html>
diff --git a/browser/base/content/test/plugins/browser_clearplugindata.js b/browser/base/content/test/plugins/browser_clearplugindata.js
new file mode 100644
index 000000000..69d474fed
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_clearplugindata.js
@@ -0,0 +1,127 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+// Test clearing plugin data using sanitize.js.
+const testURL1 = gTestRoot + "browser_clearplugindata.html";
+const testURL2 = gTestRoot + "browser_clearplugindata_noage.html";
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+const pluginHostIface = Ci.nsIPluginHost;
+var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+pluginHost.QueryInterface(pluginHostIface);
+
+var pluginTag = getTestPlugin();
+var sanitizer = null;
+
+function stored(needles) {
+ let something = pluginHost.siteHasData(this.pluginTag, null);
+ if (!needles)
+ return something;
+
+ if (!something)
+ return false;
+
+ for (let i = 0; i < needles.length; ++i) {
+ if (!pluginHost.siteHasData(this.pluginTag, needles[i]))
+ return false;
+ }
+ return true;
+}
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ if (gTestBrowser) {
+ gBrowser.removeCurrentTab();
+ }
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+
+ sanitizer = new Sanitizer();
+ sanitizer.ignoreTimespan = false;
+ sanitizer.prefDomain = "privacy.cpd.";
+ let itemPrefs = gPrefService.getBranch(sanitizer.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", true); // plugin data
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+});
+
+add_task(function* () {
+ // Load page to set data for the plugin.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testURL1);
+
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+ "Data stored for sites");
+
+ // Clear 20 seconds ago
+ let now_uSec = Date.now() * 1000;
+ sanitizer.range = [now_uSec - 20*1000000, now_uSec];
+ yield sanitizer.sanitize();
+
+ ok(stored(["bar.com", "qux.com"]), "Data stored for sites");
+ ok(!stored(["foo.com"]), "Data cleared for foo.com");
+ ok(!stored(["baz.com"]), "Data cleared for baz.com");
+
+ // Clear everything
+ sanitizer.range = null;
+ yield sanitizer.sanitize();
+
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+ gTestBrowser = null;
+});
+
+add_task(function* () {
+ // Load page to set data for the plugin.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testURL2);
+
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+ "Data stored for sites");
+
+ // Attempt to clear 20 seconds ago. The plugin will throw
+ // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us
+ // clearing all data regardless of age.
+ let now_uSec = Date.now() * 1000;
+ sanitizer.range = [now_uSec - 20*1000000, now_uSec];
+ yield sanitizer.sanitize();
+
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+ gTestBrowser = null;
+});
+
diff --git a/browser/base/content/test/plugins/browser_clearplugindata_noage.html b/browser/base/content/test/plugins/browser_clearplugindata_noage.html
new file mode 100644
index 000000000..820979541
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_clearplugindata_noage.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data sanitize test without age</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ // Make sure clearing by timerange is disabled.
+ var p = document.getElementById("plugin1");
+ p.setSitesWithDataCapabilities(false);
+
+ p.setSitesWithData(
+ "foo.com:0:5," +
+ "bar.com:0:100," +
+ "baz.com:1:5," +
+ "qux.com:1:100"
+ );
+ }
+ </script>
+ </head>
+
+ <body onload="testSteps();"></body>
+
+</html>
diff --git a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
new file mode 100644
index 000000000..bdca32e70
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -0,0 +1,34 @@
+/**
+ * Test that the notification bar for crashed GMPs works.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function* (browser) {
+ yield ContentTask.spawn(browser, null, function* () {
+ const GMP_CRASH_EVENT = {
+ pluginID: 1,
+ pluginName: "GlobalTestPlugin",
+ submittedCrashReport: false,
+ bubbles: true,
+ cancelable: true,
+ gmpPlugin: true,
+ };
+
+ let crashEvent = new content.PluginCrashedEvent("PluginCrashed",
+ GMP_CRASH_EVENT);
+ content.dispatchEvent(crashEvent);
+ });
+
+ let notification = yield waitForNotificationBar("plugin-crashed", browser);
+
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ ok(notification, "Infobar was shown.");
+ is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM,
+ "Correct priority.");
+ is(notification.getAttribute("label"),
+ "The GlobalTestPlugin plugin has crashed.",
+ "Correct message.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_pageInfo_plugins.js b/browser/base/content/test/plugins/browser_pageInfo_plugins.js
new file mode 100644
index 000000000..0d941e0fa
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pageInfo_plugins.js
@@ -0,0 +1,191 @@
+var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPageInfo = null;
+var gNextTest = null;
+var gTestBrowser = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+var gPermissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+var gTestPermissionString = gPluginHost.getPermissionStringForType("application/x-test");
+var gSecondTestPermissionString = gPluginHost.getPermissionStringForType("application/x-second-test");
+
+function doOnPageLoad(url, continuation) {
+ gNextTest = continuation;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.contentWindow.location = url;
+}
+
+function pageLoad() {
+ gTestBrowser.removeEventListener("load", pageLoad);
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function doOnOpenPageInfo(continuation) {
+ Services.obs.addObserver(pageInfoObserve, "page-info-dialog-loaded", false);
+ gNextTest = continuation;
+ // An explanation: it looks like the test harness complains about leaked
+ // windows if we don't keep a reference to every window we've opened.
+ // So, don't reuse pointers to opened Page Info windows - simply append
+ // to this list.
+ gPageInfo = BrowserPageInfo(null, "permTab");
+}
+
+function pageInfoObserve(win, topic, data) {
+ Services.obs.removeObserver(pageInfoObserve, "page-info-dialog-loaded");
+ gPageInfo.onFinished.push(() => executeSoon(gNextTest));
+}
+
+function finishTest() {
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString);
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ gBrowser.removeCurrentTab();
+
+ gPageInfo = null;
+ gNextTest = null;
+ gTestBrowser = null;
+ gPluginHost = null;
+ gPermissionManager = null;
+
+ executeSoon(finish);
+}
+
+function test() {
+ waitForExplicitFinish();
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString);
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString);
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart1a);
+}
+
+// The first test plugin is CtP and the second test plugin is enabled.
+function testPart1a() {
+ let test = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "part 1a: Test plugin should not be activated");
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA");
+ objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "part 1a: Second Test plugin should be activated");
+
+ doOnOpenPageInfo(testPart1b);
+}
+
+function testPart1b() {
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0");
+
+ is(testRadioGroup.selectedItem, testRadioDefault, "part 1b: Test radio group should be set to 'Default'");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ testRadioGroup.selectedItem = testRadioAllow;
+ testRadioAllow.doCommand();
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 1b: Second Test radio group should be set to 'Default'");
+ let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3");
+ secondtestRadioGroup.selectedItem = secondtestRadioAsk;
+ secondtestRadioAsk.doCommand();
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart2);
+}
+
+// Now, the Test plugin should be allowed, and the Test2 plugin should be CtP
+function testPart2() {
+ let test = gTestBrowser.contentDocument.getElementById("test").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(test.activated, "part 2: Test plugin should be activated");
+
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!secondtest.activated, "part 2: Second Test plugin should not be activated");
+ is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "part 2: Second test plugin should be click-to-play.");
+
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 2: Test radio group should be set to 'Allow'");
+ let testRadioBlock = gPageInfo.document.getElementById(gTestPermissionString + "#2");
+ testRadioGroup.selectedItem = testRadioBlock;
+ testRadioBlock.doCommand();
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAsk, "part 2: Second Test radio group should be set to 'Always Ask'");
+ let secondtestRadioBlock = gPageInfo.document.getElementById(gSecondTestPermissionString + "#2");
+ secondtestRadioGroup.selectedItem = secondtestRadioBlock;
+ secondtestRadioBlock.doCommand();
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart3);
+}
+
+// Now, all the things should be blocked
+function testPart3() {
+ let test = gTestBrowser.contentDocument.getElementById("test").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!test.activated, "part 3: Test plugin should not be activated");
+ is(test.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+ "part 3: Test plugin should be marked as PLUGIN_DISABLED");
+
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+
+ ok(!secondtest.activated, "part 3: Second Test plugin should not be activated");
+ is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+ "part 3: Second test plugin should be marked as PLUGIN_DISABLED");
+
+ // reset permissions
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString);
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString);
+ // check that changing the permissions affects the radio state in the
+ // open Page Info window
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0");
+ is(testRadioGroup.selectedItem, testRadioDefault, "part 3: Test radio group should be set to 'Default'");
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 3: Second Test radio group should be set to 'Default'");
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart4a);
+}
+
+// Now test that setting permission directly (as from the popup notification)
+// immediately influences Page Info.
+function testPart4a() {
+ // simulate "allow" from the doorhanger
+ gPermissionManager.add(gTestBrowser.currentURI, gTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION);
+ gPermissionManager.add(gTestBrowser.currentURI, gSecondTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ // check (again) that changing the permissions affects the radio state in the
+ // open Page Info window
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 4a: Test radio group should be set to 'Allow'");
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4a: Second Test radio group should be set to 'Always Allow'");
+
+ // now close Page Info and see that it opens with the right settings
+ gPageInfo.close();
+ doOnOpenPageInfo(testPart4b);
+}
+
+// check that "always allow" resulted in the radio buttons getting set to allow
+function testPart4b() {
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 4b: Test radio group should be set to 'Allow'");
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4b: Second Test radio group should be set to 'Allow'");
+
+ Services.prefs.setBoolPref("plugins.click_to_play", false);
+ gPageInfo.close();
+ finishTest();
+}
diff --git a/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js
new file mode 100644
index 000000000..ab4743f6f
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js
@@ -0,0 +1,207 @@
+Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var config = {};
+
+add_task(function* () {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
+ // crash reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ // Crash immediately
+ Services.prefs.setIntPref("dom.ipc.plugins.timeoutSecs", 0);
+
+ registerCleanupFunction(Task.async(function*() {
+ Services.prefs.clearUserPref("dom.ipc.plugins.timeoutSecs");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ env = null;
+ config = null;
+ gTestBrowser = null;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ }));
+});
+
+add_task(function* () {
+ config = {
+ shouldSubmissionUIBeVisible: true,
+ comment: "",
+ urlOptIn: false
+ };
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ let pluginCrashed = promisePluginCrashed();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Wait for the plugin to crash
+ yield pluginCrashed;
+
+ let crashReportStatus = TestUtils.topicObserved("crash-report-status", onSubmitStatus);
+
+ // Test that the crash submission UI is actually visible and submit the crash report.
+ yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) {
+ let doc = content.document;
+ let plugin = doc.getElementById("plugin");
+ let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit");
+ let submitButton = doc.getAnonymousElementByAttribute(plugin, "anonid", "submitButton");
+ // Test that we don't send the URL when urlOptIn is false.
+ doc.getAnonymousElementByAttribute(plugin, "anonid", "submitURLOptIn").checked = aConfig.urlOptIn;
+ submitButton.click();
+ Assert.equal(content.getComputedStyle(pleaseSubmit).display == "block",
+ aConfig.shouldSubmissionUIBeVisible, "The crash UI should be visible");
+ });
+
+ yield crashReportStatus;
+});
+
+add_task(function* () {
+ config = {
+ shouldSubmissionUIBeVisible: true,
+ comment: "a test comment",
+ urlOptIn: true
+ };
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ let pluginCrashed = promisePluginCrashed();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Wait for the plugin to crash
+ yield pluginCrashed;
+
+ let crashReportStatus = TestUtils.topicObserved("crash-report-status", onSubmitStatus);
+
+ // Test that the crash submission UI is actually visible and submit the crash report.
+ yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) {
+ let doc = content.document;
+ let plugin = doc.getElementById("plugin");
+ let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit");
+ let submitButton = doc.getAnonymousElementByAttribute(plugin, "anonid", "submitButton");
+ // Test that we send the URL when urlOptIn is true.
+ doc.getAnonymousElementByAttribute(plugin, "anonid", "submitURLOptIn").checked = aConfig.urlOptIn;
+ doc.getAnonymousElementByAttribute(plugin, "anonid", "submitComment").value = aConfig.comment;
+ submitButton.click();
+ Assert.equal(content.getComputedStyle(pleaseSubmit).display == "block",
+ aConfig.shouldSubmissionUIBeVisible, "The crash UI should be visible");
+ });
+
+ yield crashReportStatus;
+});
+
+add_task(function* () {
+ config = {
+ shouldSubmissionUIBeVisible: false,
+ comment: "",
+ urlOptIn: true
+ };
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ let pluginCrashed = promisePluginCrashed();
+
+ // Make sure that the plugin container is too small to display the crash submission UI
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html?" +
+ encodeURIComponent(JSON.stringify({width: 300, height: 300})));
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Wait for the plugin to crash
+ yield pluginCrashed;
+
+ // Test that the crash submission UI is not visible and do not submit a crash report.
+ yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) {
+ let doc = content.document;
+ let plugin = doc.getElementById("plugin");
+ let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit");
+ Assert.equal(!!pleaseSubmit && content.getComputedStyle(pleaseSubmit).display == "block",
+ aConfig.shouldSubmissionUIBeVisible, "Plugin crash UI should not be visible");
+ });
+});
+
+function promisePluginCrashed() {
+ return new ContentTask.spawn(gTestBrowser, {}, function* () {
+ yield new Promise((resolve) => {
+ addEventListener("PluginCrashReporterDisplayed", function onPluginCrashed() {
+ removeEventListener("PluginCrashReporterDisplayed", onPluginCrashed);
+ resolve();
+ });
+ });
+ })
+}
+
+function onSubmitStatus(aSubject, aData) {
+ // Wait for success or failed, doesn't matter which.
+ if (aData != "success" && aData != "failed")
+ return false;
+
+ let propBag = aSubject.QueryInterface(Ci.nsIPropertyBag);
+ if (aData == "success") {
+ let remoteID = getPropertyBagValue(propBag, "serverCrashID");
+ ok(!!remoteID, "serverCrashID should be set");
+
+ // Remove the submitted report file.
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(remoteID + ".txt");
+ ok(file.exists(), "Submitted report file should exist");
+ file.remove(false);
+ }
+
+ let extra = getPropertyBagValue(propBag, "extra");
+ ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag");
+
+ let val = getPropertyBagValue(extra, "PluginUserComment");
+ if (config.comment)
+ is(val, config.comment,
+ "Comment in extra data should match comment in textbox");
+ else
+ ok(val === undefined,
+ "Comment should be absent from extra data when textbox is empty");
+
+ val = getPropertyBagValue(extra, "PluginContentURL");
+ if (config.urlOptIn)
+ is(val, gBrowser.currentURI.spec,
+ "URL in extra data should match browser URL when opt-in checked");
+ else
+ ok(val === undefined,
+ "URL should be absent from extra data when opt-in not checked");
+
+ return true;
+}
+
+function getPropertyBagValue(bag, key) {
+ try {
+ var val = bag.getProperty(key);
+ }
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+ return val;
+}
diff --git a/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
new file mode 100644
index 000000000..42ef57314
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
@@ -0,0 +1,254 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * With e10s, plugins must run in their own process. This means we have
+ * three processes at a minimum when we're running a plugin:
+ *
+ * 1) The main browser, or "chrome" process
+ * 2) The content process hosting the plugin instance
+ * 3) The plugin process
+ *
+ * If the plugin process crashes, we cannot be sure if the chrome process
+ * will hear about it first, or the content process will hear about it
+ * first. Because of how IPC works, that's really up to the operating system,
+ * and we assume any guarantees about it, so we have to account for both
+ * possibilities.
+ *
+ * This test exercises the browser's reaction to both possibilities.
+ */
+
+const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html";
+const CRASHED_MESSAGE = "BrowserPlugins:NPAPIPluginProcessCrashed";
+
+/**
+ * In order for our test to work, we need to be able to put a plugin
+ * in a very specific state. Specifically, we need it to match the
+ * :-moz-handler-crashed pseudoselector. The only way I can find to
+ * do that is by actually crashing the plugin. So we wait for the
+ * plugin to crash and show the "please" state (since that will
+ * only show if both the message from the parent has been received
+ * AND the PluginCrashed event has fired).
+ *
+ * Once in that state, we try to rewind the clock a little bit - we clear
+ * out the crashData cache in the PluginContent with a message, and we also
+ * override the pluginFallbackState of the <object> to fool PluginContent
+ * into believing that the plugin is in a particular state.
+ *
+ * @param browser
+ * The browser that has loaded the CRASH_URL that we need to
+ * prepare to be in the special state.
+ * @param pluginFallbackState
+ * The value we should override the <object>'s pluginFallbackState
+ * with.
+ * @return Promise
+ * The Promise resolves when the plugin has officially been put into
+ * the crash reporter state, and then "rewound" to have the "status"
+ * attribute of the statusDiv removed. The resolved Promise returns
+ * the run ID for the crashed plugin. It rejects if we never get into
+ * the crash reporter state.
+ */
+function preparePlugin(browser, pluginFallbackState) {
+ return ContentTask.spawn(browser, pluginFallbackState, function* (pluginFallbackState) {
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // CRASH_URL will load a plugin that crashes immediately. We
+ // wait until the plugin has finished being put into the crash
+ // state.
+ let statusDiv;
+ yield ContentTaskUtils.waitForCondition(() => {
+ statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+ return statusDiv && statusDiv.getAttribute("status") == "please";
+ }, "Timed out waiting for plugin to be in crash report state");
+
+ // "Rewind", by wiping out the status attribute...
+ statusDiv.removeAttribute("status");
+ // Somehow, I'm able to get away with overriding the getter for
+ // this XPCOM object. Probably because I've got chrome privledges.
+ Object.defineProperty(plugin, "pluginFallbackType", {
+ get: function() {
+ return pluginFallbackState;
+ }
+ });
+ return plugin.runID;
+ }).then((runID) => {
+ browser.messageManager.sendAsyncMessage("BrowserPlugins:Test:ClearCrashData");
+ return runID;
+ });
+}
+
+add_task(function* setup() {
+ // Bypass click-to-play
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ // Clear out any minidumps we create from plugins - we really don't care
+ // about them.
+ let crashObserver = (subject, topic, data) => {
+ if (topic != "plugin-crashed") {
+ return;
+ }
+
+ let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+ let minidumpID = propBag.getPropertyAsAString("pluginDumpID");
+
+ let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ minidumpDir.append("minidumps");
+
+ let pluginDumpFile = minidumpDir.clone();
+ pluginDumpFile.append(minidumpID + ".dmp");
+
+ let extraFile = minidumpDir.clone();
+ extraFile.append(minidumpID + ".extra");
+
+ ok(pluginDumpFile.exists(), "Found minidump");
+ ok(extraFile.exists(), "Found extra file");
+
+ pluginDumpFile.remove(false);
+ extraFile.remove(false);
+ };
+
+ Services.obs.addObserver(crashObserver, "plugin-crashed", false);
+ // plugins.testmode will make BrowserPlugins:Test:ClearCrashData work.
+ Services.prefs.setBoolPref("plugins.testmode", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("plugins.testmode");
+ Services.obs.removeObserver(crashObserver, "plugin-crashed");
+ });
+});
+
+/**
+ * In this case, the chrome process hears about the crash first.
+ */
+add_task(function* testChromeHearsPluginCrashFirst() {
+ // Open a remote window so that we can run this test even if e10s is not
+ // enabled by default.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ let browser = win.gBrowser.selectedBrowser;
+
+ browser.loadURI(CRASH_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // In this case, we want the <object> to match the -moz-handler-crashed
+ // pseudoselector, but we want it to seem still active, because the
+ // content process is not yet supposed to know that the plugin has
+ // crashed.
+ let runID = yield preparePlugin(browser,
+ Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE);
+
+ // Send the message down to PluginContent.jsm saying that the plugin has
+ // crashed, and that we have a crash report.
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage(CRASHED_MESSAGE,
+ { pluginName: "", runID, state: "please" });
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // At this point, the content process should have heard the
+ // plugin crash message from the parent, and we are OK to emit
+ // the PluginCrashed event.
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+
+ if (statusDiv.getAttribute("status") == "please") {
+ Assert.ok(false, "Did not expect plugin to be in crash report mode yet.");
+ return;
+ }
+
+ // Now we need the plugin to seem crashed to PluginContent.jsm, without
+ // actually crashing the plugin again. We hack around this by overriding
+ // the pluginFallbackType again.
+ Object.defineProperty(plugin, "pluginFallbackType", {
+ get: function() {
+ return Ci.nsIObjectLoadingContent.PLUGIN_CRASHED;
+ },
+ });
+
+ let event = new content.PluginCrashedEvent("PluginCrashed", {
+ pluginName: "",
+ pluginDumpID: "",
+ browserDumpID: "",
+ submittedCrashReport: false,
+ bubbles: true,
+ cancelable: true,
+ });
+
+ plugin.dispatchEvent(event);
+ Assert.equal(statusDiv.getAttribute("status"), "please",
+ "Should have been showing crash report UI");
+ });
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * In this case, the content process hears about the crash first.
+ */
+add_task(function* testContentHearsCrashFirst() {
+ // Open a remote window so that we can run this test even if e10s is not
+ // enabled by default.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ let browser = win.gBrowser.selectedBrowser;
+
+ browser.loadURI(CRASH_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // In this case, we want the <object> to match the -moz-handler-crashed
+ // pseudoselector, and we want the plugin to seem crashed, since the
+ // content process in this case has heard about the crash first.
+ let runID = yield preparePlugin(browser,
+ Ci.nsIObjectLoadingContent.PLUGIN_CRASHED);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // At this point, the content process has not yet heard from the
+ // parent about the crash report. Let's ensure that by making sure
+ // we're not showing the plugin crash report UI.
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+
+ if (statusDiv.getAttribute("status") == "please") {
+ Assert.ok(false, "Did not expect plugin to be in crash report mode yet.");
+ }
+
+ let event = new content.PluginCrashedEvent("PluginCrashed", {
+ pluginName: "",
+ pluginDumpID: "",
+ browserDumpID: "",
+ submittedCrashReport: false,
+ bubbles: true,
+ cancelable: true,
+ });
+
+ plugin.dispatchEvent(event);
+
+ Assert.notEqual(statusDiv.getAttribute("status"), "please",
+ "Should not yet be showing crash report UI");
+ });
+
+ // Now send the message down to PluginContent.jsm that the plugin has
+ // crashed...
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage(CRASHED_MESSAGE,
+ { pluginName: "", runID, state: "please"});
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // At this point, the content process will have heard the message
+ // from the parent and reacted to it. We should be showing the plugin
+ // crash report UI now.
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+
+ Assert.equal(statusDiv.getAttribute("status"), "please",
+ "Should have been showing crash report UI");
+ });
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/plugins/browser_plugin_reloading.js b/browser/base/content/test/plugins/browser_plugin_reloading.js
new file mode 100644
index 000000000..7327d4cf9
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_plugin_reloading.js
@@ -0,0 +1,85 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+function updateAllTestPlugins(aState) {
+ setTestPluginEnabledState(aState, "Test Plug-in");
+ setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gTestBrowser = null;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ }));
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests that a click-to-play plugin retains its activated state upon reloading
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 1, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 2, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ // run the plugin
+ yield promisePlayObject("test");
+
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 3, plugin should have started");
+ ok(pluginInfo.activated, "Test 4, plugin node should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let npobj1 = Components.utils.waiveXrays(plugin).getObjectValue();
+ plugin.src = plugin.src;
+ let pluginsDiffer = false;
+ try {
+ Components.utils.waiveXrays(plugin).checkObjectValue(npobj1);
+ } catch (e) {
+ pluginsDiffer = true;
+ }
+
+ Assert.ok(pluginsDiffer, "Test 5, plugins differ.");
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 6, Plugin should have retained activated state.");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 7, plugin should have started");
+});
diff --git a/browser/base/content/test/plugins/browser_pluginnotification.js b/browser/base/content/test/plugins/browser_pluginnotification.js
new file mode 100644
index 000000000..bf32f37a4
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginnotification.js
@@ -0,0 +1,626 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+function updateAllTestPlugins(aState) {
+ setTestPluginEnabledState(aState, "Test Plug-in");
+ setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gTestBrowser = null;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ }));
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests a page with an unknown plugin in it.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_unknown.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("unknown");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED,
+ "Test 1a, plugin fallback type should be PLUGIN_UNSUPPORTED");
+});
+
+// Test that the doorhanger is shown when the user clicks on the overlay
+// after having previously blocked the plugin.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Plugin should not be activated");
+
+ // Simulate clicking the "Allow Now" button.
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._secondaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Plugin should be activated");
+
+ // Simulate clicking the "Block" button.
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Plugin should not be activated");
+
+ // Simulate clicking the overlay
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ ok(!notification.dismissed, "A plugin notification should be shown.");
+
+ clearAllPluginPermissions();
+});
+
+// Tests that going back will reshow the notification for click-to-play plugins
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+
+ // make sure the notification is gone
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 11b, Should not have a click-to-play notification");
+
+ gTestBrowser.webNavigation.goBack();
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+// Tests that the "Allow Always" permission works for click-to-play plugins
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 12a, Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 12a, Plugin should be activated");
+});
+
+// Test that the "Always" permission, when set for just the Test plugin,
+// does not also allow the Second Test plugin.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let test = content.document.getElementById("test");
+ let secondtestA = content.document.getElementById("secondtestA");
+ let secondtestB = content.document.getElementById("secondtestB");
+ Assert.ok(test.activated && !secondtestA.activated && !secondtestB.activated,
+ "Content plugins are set up");
+ });
+
+ clearAllPluginPermissions();
+});
+
+// Tests that the plugin's "activated" property is true for working plugins
+// with click-to-play disabled.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test2.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test1");
+ ok(pluginInfo.activated, "Test 14, Plugin should be activated");
+});
+
+// Tests that the overlay is shown instead of alternate content when
+// plugins are click to play.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_alternate_content.html");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!!mainBox, "Test 15, Plugin overlay should exist");
+ });
+});
+
+// Tests that mContentType is used for click-to-play plugins, and not the
+// inspected type.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug749455.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 17, Should have a click-to-play notification");
+});
+
+// Tests that clicking the icon of the overlay activates the doorhanger
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+ "Test 19a, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let icon = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
+ let bounds = icon.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ yield promiseForCondition(condition);
+});
+
+// Tests that clicking the text of the overlay activates the plugin
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+ "Test 19c, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let text = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
+ let bounds = text.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ yield promiseForCondition(condition);
+});
+
+// Tests that clicking the box of the overlay activates the doorhanger
+// (just to be thorough)
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+ "Test 19e, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", 50, 50, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", 50, 50, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 19e, Plugin should not be activated");
+
+ clearAllPluginPermissions();
+});
+
+// Tests that a plugin in a div that goes from style="display: none" to
+// "display: block" can be clicked to activate.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_hidden_to_visible.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 20a, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!!overlay, "Test 20a, Plugin overlay should exist");
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let overlayRect = mainBox.getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 20a, plugin should have an overlay with 0px width and height");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 20b, plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let div = doc.getElementById("container");
+ Assert.equal(div.style.display, "none",
+ "Test 20b, container div should be display: none");
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let div = doc.getElementById("container");
+ div.style.display = "block";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let overlayRect = mainBox.getBoundingClientRect();
+ Assert.ok(overlayRect.width == 200 && overlayRect.height == 200,
+ "Test 20c, plugin should have overlay dims of 200px");
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 20b, plugin should not be activated");
+
+ ok(notification.dismissed, "Test 20c, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !notification.dismissed && !!PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 20c, plugin should be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 20c, plugin should have overlay dims of 0px");
+ });
+
+ clearAllPluginPermissions();
+});
+
+// Test having multiple different types of plugin on one page
+add_task(function* () {
+ // contains three plugins, application/x-test, application/x-second-test x 2
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21a, Should have a click-to-play notification");
+
+ // confirm all three are blocked by ctp at this point
+ let ids = ["test", "secondtestA", "secondtestB"];
+ for (let id of ids) {
+ yield ContentTask.spawn(gTestBrowser, { id }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById(args.id);
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 200 && overlayRect.height == 200,
+ "Test 21a, plugin " + args.id + " should have click-to-play overlay with dims");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo(id);
+ ok(!pluginInfo.activated, "Test 21a, Plugin with id=" + id + " should not be activated");
+ }
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21a, Should have a click-to-play notification");
+
+ // we have to actually show the panel to get the bindings to instantiate
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 2, "Test 21a, Should have two types of plugin in the notification");
+
+ let centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 21b, found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 21b, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 21b, found center item for the Test plugin");
+
+ // Select the allow now option in the select drop down for Test Plugin
+ centerItem.value = "allownow";
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 21b, plugin should be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21b, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ ok(notification.options.pluginData.size == 2, "Test 21c, Should have one type of plugin in the notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 21c, plugin should have overlay dims of 0px");
+ });
+
+ ids = ["secondtestA", "secondtestB"];
+ for (let id of ids) {
+ yield ContentTask.spawn(gTestBrowser, { id }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById(args.id);
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 200 && overlayRect.height == 200,
+ "Test 21c, plugin " + args.id + " should have click-to-play overlay with zero dims");
+ });
+
+
+ let pluginInfo = yield promiseForPluginInfo(id);
+ ok(!pluginInfo.activated, "Test 21c, Plugin with id=" + id + " should not be activated");
+ }
+
+ centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Second Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 21d, found center action for the Second Test plugin");
+
+ centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action == centerAction) {
+ is(item.value, "block", "Test 21d, test plugin 2 should start blocked");
+ centerItem = item;
+ break;
+ }
+ else {
+ is(item.value, "allownow", "Test 21d, test plugin should be enabled");
+ }
+ }
+ ok(centerItem, "Test 21d, found center item for the Second Test plugin");
+
+ // Select the allow now option in the select drop down for Second Test Plguins
+ centerItem.value = "allownow";
+
+ // "click" the button to activate the Second Test plugins
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21d, Should have a click-to-play notification");
+
+ ids = ["test", "secondtestA", "secondtestB"];
+ for (let id of ids) {
+ yield ContentTask.spawn(gTestBrowser, { id }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById(args.id);
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 21d, plugin " + args.id + " should have click-to-play overlay with zero dims");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo(id);
+ ok(pluginInfo.activated, "Test 21d, Plugin with id=" + id + " should not be activated");
+ }
+});
+
+// Tests that a click-to-play plugin resets its activated state when changing types
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 22, Should have a click-to-play notification");
+
+ // Plugin should start as CTP
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.type = null;
+ // We currently don't properly change state just on type change,
+ // so rebind the plugin to tree. bug 767631
+ plugin.parentNode.appendChild(plugin);
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.type = "application/x-test";
+ plugin.parentNode.appendChild(plugin);
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ ok(!pluginInfo.activated, "Test 23, plugin node should not be activated");
+});
+
+// Plugin sync removal test. Note this test produces a notification drop down since
+// the plugin we add has zero dims.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html");
+
+ // Maybe there some better trick here, we need to wait for the page load, then
+ // wait for the js to execute in the page.
+ yield waitForMs(500);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
+ ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+});
+
+// Tests a page with a blocked plugin in it and make sure the infoURL property
+// the blocklist file gets used.
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+ // Since the plugin notification is dismissed by default, reshow it.
+ yield promiseForNotificationShown(notification);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
+ "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED");
+ ok(!pluginInfo.activated, "Plugin should be activated.");
+
+ const testUrl = "http://test.url.com/";
+
+ let firstPanelChild = PopupNotifications.panel.firstChild;
+ let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid",
+ "click-to-play-plugins-notification-link");
+ is(infoLink.href, testUrl,
+ "Test 26, the notification URL needs to match the infoURL from the blocklist file.");
+});
diff --git a/browser/base/content/test/plugins/browser_plugins_added_dynamically.js b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js
new file mode 100644
index 000000000..22077a54d
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js
@@ -0,0 +1,137 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+// "Activate" of a given type -> plugins of that type dynamically added should
+// automatically play.
+add_task(function* () {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 1a, Should not have a click-to-play notification");
+
+ // Add a plugin of type test
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test"));
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 1a, Should not have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ let centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 2, found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 3, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 4, found center item for the Test plugin");
+
+ // Select the allow now option in the select drop down for Test Plugin
+ centerItem.value = "allownow";
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(pluginInfo.activated, "Test 5, plugin should be activated");
+
+ // Add another plugin of type test
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-test"));
+ });
+
+ pluginInfo = yield promiseForPluginInfo("plugintwo");
+ ok(pluginInfo.activated, "Test 6, plugins should be activated");
+});
+
+// "Activate" of a given type -> plugins of other types dynamically added
+// should not automatically play.
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 7, Should not have a click-to-play notification");
+
+ // Add a plugin of type test
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test"));
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 8, Should not have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 1, "Should be one plugin action");
+
+ let pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(!pluginInfo.activated, "Test 8, test plugin should be activated");
+
+ let condition = () => !notification.dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(pluginInfo.activated, "Test 9, test plugin should be activated");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-second-test"));
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(pluginInfo.activated, "Test 10, plugins should be activated");
+ pluginInfo = yield promiseForPluginInfo("plugintwo");
+ ok(!pluginInfo.activated, "Test 11, plugins should be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_private_clicktoplay.js b/browser/base/content/test/plugins/browser_private_clicktoplay.js
new file mode 100644
index 000000000..785b1bb31
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_private_clicktoplay.js
@@ -0,0 +1,216 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gPrivateWindow = null;
+var gPrivateBrowser = null;
+
+function finishTest() {
+ clearAllPluginPermissions();
+ gBrowser.removeCurrentTab();
+ if (gPrivateWindow) {
+ gPrivateWindow.close();
+ }
+ window.focus();
+}
+
+let createPrivateWindow = Task.async(function* createPrivateWindow(url) {
+ gPrivateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ ok(!!gPrivateWindow, "should have created a private window.");
+ gPrivateBrowser = gPrivateWindow.getBrowser().selectedBrowser;
+
+ BrowserTestUtils.loadURI(gPrivateBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gPrivateBrowser);
+});
+
+add_task(function* test() {
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ let promise = BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ yield promise;
+});
+
+add_task(function* test1a() {
+ yield createPrivateWindow(gHttpTestRoot + "plugin_test.html");
+});
+
+add_task(function* test1b() {
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 1b, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gPrivateBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+ });
+
+ // Check the button status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+
+ yield promiseShown;
+ let button1 = gPrivateWindow.PopupNotifications.panel.firstChild._primaryButton;
+ let button2 = gPrivateWindow.PopupNotifications.panel.firstChild._secondaryButton;
+ is(button1.getAttribute("action"), "_singleActivateNow", "Test 1b, Blocked plugin in private window should have a activate now button");
+ ok(button2.hidden, "Test 1b, Blocked plugin in a private window should not have a secondary button")
+
+ gPrivateWindow.close();
+ BrowserTestUtils.loadURI(gTestBrowser, gHttpTestRoot + "plugin_test.html");
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* test2a() {
+ // enable test plugin on this site
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 2a, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 2a, Plugin should not be activated");
+ });
+
+ // Simulate clicking the "Allow Now" button.
+ let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+
+ PopupNotifications.panel.firstChild._secondaryButton.click();
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let condition = () => objLoadingContent.activated;
+ yield ContentTaskUtils.waitForCondition(condition, "Test 2a, Waited too long for plugin to activate");
+ });
+});
+
+add_task(function* test2c() {
+ let topicObserved = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+ yield createPrivateWindow(gHttpTestRoot + "plugin_test.html");
+ yield topicObserved;
+
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 2c, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gPrivateBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 2c, Plugin should be activated");
+ });
+
+ // Check the button status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer;
+ ok(buttonContainer.hidden, "Test 2c, Activated plugin in a private window should not have visible buttons");
+
+ clearAllPluginPermissions();
+ gPrivateWindow.close();
+
+ BrowserTestUtils.loadURI(gTestBrowser, gHttpTestRoot + "plugin_test.html");
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* test3a() {
+ // enable test plugin on this site
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 3a, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 3a, Plugin should not be activated");
+ });
+
+ // Simulate clicking the "Allow Always" button.
+ let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ PopupNotifications.panel.firstChild._secondaryButton.click();
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let condition = () => objLoadingContent.activated;
+ yield ContentTaskUtils.waitForCondition(condition, "Test 3a, Waited too long for plugin to activate");
+ });
+});
+
+add_task(function* test3c() {
+ let topicObserved = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+ yield createPrivateWindow(gHttpTestRoot + "plugin_test.html");
+ yield topicObserved;
+
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 3c, Should have a click-to-play notification");
+
+ // Check the button status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer;
+ ok(buttonContainer.hidden, "Test 3c, Activated plugin in a private window should not have visible buttons");
+
+ BrowserTestUtils.loadURI(gPrivateBrowser, gHttpTestRoot + "plugin_two_types.html");
+ yield BrowserTestUtils.browserLoaded(gPrivateBrowser);
+});
+
+add_task(function* test3d() {
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 3d, Should have a click-to-play notification");
+
+ // Check the list item status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ let doc = gPrivateWindow.document;
+ for (let item of gPrivateWindow.PopupNotifications.panel.firstChild.childNodes) {
+ let allowalways = doc.getAnonymousElementByAttribute(item, "anonid", "allowalways");
+ ok(allowalways, "Test 3d, should have list item for allow always");
+ let allownow = doc.getAnonymousElementByAttribute(item, "anonid", "allownow");
+ ok(allownow, "Test 3d, should have list item for allow now");
+ let block = doc.getAnonymousElementByAttribute(item, "anonid", "block");
+ ok(block, "Test 3d, should have list item for block");
+
+ if (item.action.pluginName === "Test") {
+ is(item.value, "allowalways", "Test 3d, Plugin should bet set to 'allow always'");
+ ok(!allowalways.hidden, "Test 3d, Plugin set to 'always allow' should have a visible 'always allow' action.");
+ ok(allownow.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'allow now' action.");
+ ok(block.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'block' action.");
+ } else if (item.action.pluginName === "Second Test") {
+ is(item.value, "block", "Test 3d, Second plugin should bet set to 'block'");
+ ok(allowalways.hidden, "Test 3d, Plugin set to 'block' should have a visible 'always allow' action.");
+ ok(!allownow.hidden, "Test 3d, Plugin set to 'block' should have a visible 'allow now' action.");
+ ok(!block.hidden, "Test 3d, Plugin set to 'block' should have a visible 'block' action.");
+ } else {
+ ok(false, "Test 3d, Unexpected plugin '"+item.action.pluginName+"'");
+ }
+ }
+
+ finishTest();
+});
diff --git a/browser/base/content/test/plugins/head.js b/browser/base/content/test/plugins/head.js
new file mode 100644
index 000000000..4995c4dc6
--- /dev/null
+++ b/browser/base/content/test/plugins/head.js
@@ -0,0 +1,396 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+
+// The blocklist shim running in the content process does not initialize at
+// start up, so it's not active until we load content that needs to do a
+// check. This helper bypasses the delay to get the svc up and running
+// immediately. Note, call this after remote content has loaded.
+function promiseInitContentBlocklistSvc(aBrowser)
+{
+ return ContentTask.spawn(aBrowser, {}, function* () {
+ try {
+ Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsIBlocklistService);
+ } catch (ex) {
+ return ex.message;
+ }
+ return null;
+ });
+}
+
+/**
+ * Waits a specified number of miliseconds.
+ *
+ * Usage:
+ * let wait = yield waitForMs(2000);
+ * ok(wait, "2 seconds should now have elapsed");
+ *
+ * @param aMs the number of miliseconds to wait for
+ * @returns a Promise that resolves to true after the time has elapsed
+ */
+function waitForMs(aMs) {
+ return new Promise((resolve) => {
+ setTimeout(done, aMs);
+ function done() {
+ resolve(true);
+ }
+ });
+}
+
+function waitForEvent(subject, eventName, checkFn, useCapture, useUntrusted) {
+ return new Promise((resolve, reject) => {
+ subject.addEventListener(eventName, function listener(event) {
+ try {
+ if (checkFn && !checkFn(event)) {
+ return;
+ }
+ subject.removeEventListener(eventName, listener, useCapture);
+ resolve(event);
+ } catch (ex) {
+ try {
+ subject.removeEventListener(eventName, listener, useCapture);
+ } catch (ex2) {
+ // Maybe the provided object does not support removeEventListener.
+ }
+ reject(ex);
+ }
+ }, useCapture, useUntrusted);
+ });
+}
+
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url)
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+ return loaded;
+}
+
+function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) {
+ let tries = 0;
+ let maxTries = aTries || 100; // 100 tries
+ let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds
+ let interval = setInterval(function() {
+ if (tries >= maxTries) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ let conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, maxWait);
+ let moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+// Waits for a conditional function defined by the caller to return true.
+function promiseForCondition(aConditionFn, aMessage, aTries, aWait) {
+ return new Promise((resolve) => {
+ waitForCondition(aConditionFn, resolve,
+ (aMessage || "Condition didn't pass."),
+ aTries, aWait);
+ });
+}
+
+// Returns the chrome side nsIPluginTag for this plugin
+function getTestPlugin(aName) {
+ let pluginName = aName || "Test Plug-in";
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (let i = 0; i < tags.length; i++) {
+ if (tags[i].name == pluginName)
+ return tags[i];
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
+// Set the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ let name = pluginName || "Test Plug-in";
+ let plugin = getTestPlugin(name);
+ plugin.enabledState = newEnabledState;
+}
+
+// Get the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function getTestPluginEnabledState(pluginName) {
+ let name = pluginName || "Test Plug-in";
+ let plugin = getTestPlugin(name);
+ return plugin.enabledState;
+}
+
+// Returns a promise for nsIObjectLoadingContent props data.
+function promiseForPluginInfo(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ throw new Error("no plugin found");
+ return {
+ pluginFallbackType: plugin.pluginFallbackType,
+ activated: plugin.activated,
+ hasRunningPlugin: plugin.hasRunningPlugin,
+ displayedType: plugin.displayedType,
+ };
+ });
+}
+
+// Return a promise and call the plugin's nsIObjectLoadingContent
+// playPlugin() method.
+function promisePlayObject(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ objLoadingContent.playPlugin();
+ });
+}
+
+function promiseCrashObject(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ Components.utils.waiveXrays(plugin).crash();
+ });
+}
+
+// Return a promise and call the plugin's getObjectValue() method.
+function promiseObjectValueResult(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ return Components.utils.waiveXrays(plugin).getObjectValue();
+ });
+}
+
+// Return a promise and reload the target plugin in the page
+function promiseReloadPlugin(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ plugin.src = plugin.src;
+ });
+}
+
+// after a test is done using the plugin doorhanger, we should just clear
+// any permissions that may have crept in
+function clearAllPluginPermissions() {
+ let perms = Services.perms.enumerator;
+ while (perms.hasMoreElements()) {
+ let perm = perms.getNext();
+ if (perm.type.startsWith('plugin')) {
+ info("removing permission:" + perm.principal.origin + " " + perm.type + "\n");
+ Services.perms.removePermission(perm);
+ }
+ }
+}
+
+function updateBlocklist(aCallback) {
+ let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ let observer = function() {
+ Services.obs.removeObserver(observer, "blocklist-updated");
+ SimpleTest.executeSoon(aCallback);
+ };
+ Services.obs.addObserver(observer, "blocklist-updated", false);
+ blocklistNotifier.notify(null);
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL, aCallback) {
+ if (!_originalTestBlocklistURL) {
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ }
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ updateBlocklist(aCallback);
+}
+
+// A generator that insures a new blocklist is loaded (in both
+// processes if applicable).
+function* asyncSetAndUpdateBlocklist(aURL, aBrowser) {
+ info("*** loading new blocklist: " + aURL);
+ let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false;
+ if (!_originalTestBlocklistURL) {
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ }
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ let localPromise = TestUtils.topicObserved("blocklist-updated");
+ let remotePromise;
+ if (doTestRemote) {
+ remotePromise = TestUtils.topicObserved("content-blocklist-updated");
+ }
+ let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ blocklistNotifier.notify(null);
+ info("*** waiting on local load");
+ yield localPromise;
+ if (doTestRemote) {
+ info("*** waiting on remote load");
+ yield remotePromise;
+ }
+ info("*** blocklist loaded.");
+}
+
+// Reset back to the blocklist we had at the start of the test run.
+function resetBlocklist() {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+}
+
+// Insure there's a popup notification present. This test does not indicate
+// open state. aBrowser can be undefined.
+function promisePopupNotification(aName, aBrowser) {
+ return new Promise((resolve) => {
+ waitForCondition(() => PopupNotifications.getNotification(aName, aBrowser),
+ () => {
+ ok(!!PopupNotifications.getNotification(aName, aBrowser),
+ aName + " notification appeared");
+
+ resolve();
+ }, "timeout waiting for popup notification " + aName);
+ });
+}
+
+/**
+ * Allows setting focus on a window, and waiting for that window to achieve
+ * focus.
+ *
+ * @param aWindow
+ * The window to focus and wait for.
+ *
+ * @return {Promise}
+ * @resolves When the window is focused.
+ * @rejects Never.
+ */
+function promiseWaitForFocus(aWindow) {
+ return new Promise((resolve) => {
+ waitForFocus(resolve, aWindow);
+ });
+}
+
+/**
+ * Returns a Promise that resolves when a notification bar
+ * for a browser is shown. Alternatively, for old-style callers,
+ * can automatically call a callback before it resolves.
+ *
+ * @param notificationID
+ * The ID of the notification to look for.
+ * @param browser
+ * The browser to check for the notification bar.
+ * @param callback (optional)
+ * A function to be called just before the Promise resolves.
+ *
+ * @return Promise
+ */
+function waitForNotificationBar(notificationID, browser, callback) {
+ return new Promise((resolve, reject) => {
+ let notification;
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ waitForCondition(
+ () => (notification = notificationBox.getNotificationWithValue(notificationID)),
+ () => {
+ ok(notification, `Successfully got the ${notificationID} notification bar`);
+ if (callback) {
+ callback(notification);
+ }
+ resolve(notification);
+ },
+ `Waited too long for the ${notificationID} notification bar`
+ );
+ });
+}
+
+function promiseForNotificationBar(notificationID, browser) {
+ return new Promise((resolve) => {
+ waitForNotificationBar(notificationID, browser, resolve);
+ });
+}
+
+/**
+ * Reshow a notification and call a callback when it is reshown.
+ * @param notification
+ * The notification to reshow
+ * @param callback
+ * A function to be called when the notification has been reshown
+ */
+function waitForNotificationShown(notification, callback) {
+ if (PopupNotifications.panel.state == "open") {
+ executeSoon(callback);
+ return;
+ }
+ PopupNotifications.panel.addEventListener("popupshown", function onShown(e) {
+ PopupNotifications.panel.removeEventListener("popupshown", onShown);
+ callback();
+ }, false);
+ notification.reshow();
+}
+
+function promiseForNotificationShown(notification) {
+ return new Promise((resolve) => {
+ waitForNotificationShown(notification, resolve);
+ });
+}
+
+/**
+ * Due to layout being async, "PluginBindAttached" may trigger later. This
+ * returns a Promise that resolves once we've forced a layout flush, which
+ * triggers the PluginBindAttached event to fire. This trick only works if
+ * there is some sort of plugin in the page.
+ * @param browser
+ * The browser to force plugin bindings in.
+ * @return Promise
+ */
+function promiseUpdatePluginBindings(browser) {
+ return ContentTask.spawn(browser, {}, function* () {
+ let doc = content.document;
+ let elems = doc.getElementsByTagName('embed');
+ if (!elems || elems.length < 1) {
+ elems = doc.getElementsByTagName('object');
+ }
+ if (elems && elems.length > 0) {
+ elems[0].clientTop;
+ }
+ });
+}
diff --git a/browser/base/content/test/plugins/plugin_add_dynamically.html b/browser/base/content/test/plugins/plugin_add_dynamically.html
new file mode 100644
index 000000000..863d36e09
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_add_dynamically.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script>
+function addPlugin(aId, aType="application/x-test") {
+ var embed = document.createElement("embed");
+ embed.setAttribute("id", aId);
+ embed.style.width = "200px";
+ embed.style.height = "200px";
+ embed.setAttribute("type", aType);
+ return document.body.appendChild(embed);
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_alternate_content.html b/browser/base/content/test/plugins/plugin_alternate_content.html
new file mode 100644
index 000000000..f8acc833c
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_alternate_content.html
@@ -0,0 +1,9 @@
+<!-- bug 739575 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<object id="test" type="application/x-test" style="height: 200px; width:200px">
+<p><a href="about:blank">you should not see this link when plugins are click-to-play</a></p>
+</object>
+</body></html>
diff --git a/browser/base/content/test/plugins/plugin_big.html b/browser/base/content/test/plugins/plugin_big.html
new file mode 100644
index 000000000..d11506176
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_big.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 500px; height: 500px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_both.html b/browser/base/content/test/plugins/plugin_both.html
new file mode 100644
index 000000000..2335366dc
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_both.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+<embed id="test" style="width: 100px; height: 100px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_both2.html b/browser/base/content/test/plugins/plugin_both2.html
new file mode 100644
index 000000000..ba605d6e8
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_both2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 100px; height: 100px" type="application/x-test">
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug744745.html b/browser/base/content/test/plugins/plugin_bug744745.html
new file mode 100644
index 000000000..d0691c9c0
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug744745.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<style>
+.x {
+ opacity: 0 !important;
+}
+</style>
+<object id="test" class="x" type="application/x-test" width=200 height=200></object>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug749455.html b/browser/base/content/test/plugins/plugin_bug749455.html
new file mode 100644
index 000000000..831dc82f7
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug749455.html
@@ -0,0 +1,8 @@
+<!-- bug 749455 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<embed src="plugin_bug749455.html" type="application/x-test" width="100px" height="100px"></embed>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug787619.html b/browser/base/content/test/plugins/plugin_bug787619.html
new file mode 100644
index 000000000..cb91116f0
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug787619.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+ <a id="wrapper">
+ <embed id="plugin" style="width: 200px; height: 200px" type="application/x-test">
+ </a>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug797677.html b/browser/base/content/test/plugins/plugin_bug797677.html
new file mode 100644
index 000000000..1545f3647
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug797677.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body><embed id="plugin" type="9000"></embed></body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug820497.html b/browser/base/content/test/plugins/plugin_bug820497.html
new file mode 100644
index 000000000..4884e9dbe
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug820497.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<object id="test" type="application/x-test" width=200 height=200></object>
+<script>
+ function addSecondPlugin() {
+ var object = document.createElement("object");
+ object.type = "application/x-second-test";
+ object.width = 200;
+ object.height = 200;
+ object.id = "secondtest";
+ document.body.appendChild(object);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_clickToPlayAllow.html b/browser/base/content/test/plugins/plugin_clickToPlayAllow.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_clickToPlayAllow.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_clickToPlayDeny.html b/browser/base/content/test/plugins/plugin_clickToPlayDeny.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_clickToPlayDeny.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_crashCommentAndURL.html b/browser/base/content/test/plugins/plugin_crashCommentAndURL.html
new file mode 100644
index 000000000..711a19ed3
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_crashCommentAndURL.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+ function crash() {
+ var plugin = document.getElementById("plugin");
+ var argStr = decodeURIComponent(window.location.search.substr(1));
+ if (argStr) {
+ var args = JSON.parse(argStr);
+ for (var key in args)
+ plugin.setAttribute(key, args[key]);
+ }
+ try {
+ plugin.crash();
+ }
+ catch (err) {}
+ }
+ </script>
+ </head>
+ <body onload="crash();">
+ <embed id="plugin" type="application/x-test"
+ width="400" height="400"
+ drawmode="solid" color="FF00FFFF">
+ </embed>
+ </body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_data_url.html b/browser/base/content/test/plugins/plugin_data_url.html
new file mode 100644
index 000000000..77e101144
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_data_url.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+ <a id="data-link-1" href='data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>'>
+ data: with one plugin
+ </a><br />
+ <a id="data-link-2" href='data:text/html,<embed id="test1" style="width: 200px; height: 200px" type="application/x-test"/><embed id="test2" style="width: 200px; height: 200px" type="application/x-second-test"/>'>
+ data: with two plugins
+ </a><br />
+ <object id="test" style="width: 200px; height: 200px" type="application/x-test"></object>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_hidden_to_visible.html b/browser/base/content/test/plugins/plugin_hidden_to_visible.html
new file mode 100644
index 000000000..eeacc1874
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_hidden_to_visible.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+ <div id="container" style="display: none">
+ <object id="test" type="application/x-test" style="width: 200px; height: 200px;"></object>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_iframe.html b/browser/base/content/test/plugins/plugin_iframe.html
new file mode 100644
index 000000000..239c9a771
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_iframe.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<iframe id="frame" with="400" height="400" src="plugin_test.html">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_outsideScrollArea.html b/browser/base/content/test/plugins/plugin_outsideScrollArea.html
new file mode 100644
index 000000000..c6ef50d5d
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_outsideScrollArea.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style type="text/css">
+#container {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ background: blue;
+}
+
+#test {
+ width: 400px;
+ height: 400px;
+ position: absolute;
+}
+</style>
+</head>
+<body>
+ <div id="container"></div>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_overlayed.html b/browser/base/content/test/plugins/plugin_overlayed.html
new file mode 100644
index 000000000..11c127093
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_overlayed.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ .absthing {
+ width: 400px;
+ height: 400px;
+ position: absolute;
+ left: 20px;
+ top: 20px;
+ }
+ #d1 {
+ z-index: 1;
+ }
+ #d2 {
+ z-index: 2;
+ background-color: rgba(0,0,255,0.5);
+ border: 1px solid red;
+ }
+ </style>
+<body>
+ <div class="absthing" id="d1">
+ <embed id="test" type="application/x-test">
+ </div>
+ <div class="absthing" id="d2">
+ <p>This is overlaying
+ </div>
diff --git a/browser/base/content/test/plugins/plugin_positioned.html b/browser/base/content/test/plugins/plugin_positioned.html
new file mode 100644
index 000000000..1bad7ee46
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_positioned.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ #test {
+ position: absolute;
+ left: -1000px;
+ top: -1000px;
+ }
+ </style>
+<body>
+ <embed id="test" type="application/x-test">
diff --git a/browser/base/content/test/plugins/plugin_small.html b/browser/base/content/test/plugins/plugin_small.html
new file mode 100644
index 000000000..f37ee28c7
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_small.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 10px; height: 10px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_small_2.html b/browser/base/content/test/plugins/plugin_small_2.html
new file mode 100644
index 000000000..ebc5ffe84
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_small_2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 10px; height: 10px" type="application/x-second-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_syncRemoved.html b/browser/base/content/test/plugins/plugin_syncRemoved.html
new file mode 100644
index 000000000..d97787056
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_syncRemoved.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<body>
+<script type="text/javascript">
+ // create an embed, insert it in the doc and immediately remove it
+ var embed = document.createElement('embed');
+ embed.setAttribute("id", "test");
+ embed.setAttribute("type", "application/x-test");
+ embed.setAttribute("style", "width: 0px; height: 0px;");
+ document.body.appendChild(embed);
+ window.getComputedStyle(embed, null).top;
+ document.body.remove(embed);
+</script>
diff --git a/browser/base/content/test/plugins/plugin_test.html b/browser/base/content/test/plugins/plugin_test.html
new file mode 100644
index 000000000..d4b5b6ca7
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_test2.html b/browser/base/content/test/plugins/plugin_test2.html
new file mode 100644
index 000000000..95614c930
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_test2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test1" style="width: 200px; height: 200px" type="application/x-test">
+<embed id="test2" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_test3.html b/browser/base/content/test/plugins/plugin_test3.html
new file mode 100644
index 000000000..215c02326
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_test3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 0px; height: 0px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_two_types.html b/browser/base/content/test/plugins/plugin_two_types.html
new file mode 100644
index 000000000..2359d2ec1
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_two_types.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>
+<embed id="secondtestA" style="width: 200px; height: 200px" type="application/x-second-test"/>
+<embed id="secondtestB" style="width: 200px; height: 200px" type="application/x-second-test"/>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_unknown.html b/browser/base/content/test/plugins/plugin_unknown.html
new file mode 100644
index 000000000..578f455cc
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_unknown.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_zoom.html b/browser/base/content/test/plugins/plugin_zoom.html
new file mode 100644
index 000000000..f9e598658
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_zoom.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<!-- The odd width and height are here to trigger bug 972237. -->
+<embed id="test" style="width: 99.789%; height: 99.123%" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/popupNotifications/.eslintrc.js b/browser/base/content/test/popupNotifications/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/popupNotifications/browser.ini b/browser/base/content/test/popupNotifications/browser.ini
new file mode 100644
index 000000000..83bb7c517
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_displayURI.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_2.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_3.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_4.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_checkbox.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_reshow_in_background.js]
+skip-if = (os == "linux" && (debug || asan))
diff --git a/browser/base/content/test/popupNotifications/browser_displayURI.js b/browser/base/content/test/popupNotifications/browser_displayURI.js
new file mode 100644
index 000000000..48222be19
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_displayURI.js
@@ -0,0 +1,28 @@
+/*
+ * Make sure that the origin is shown for ContentPermissionPrompt
+ * consumers e.g. geolocation.
+*/
+
+add_task(function* test_displayURI() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "https://test1.example.com/",
+ }, function*(browser) {
+ let popupShownPromise = new Promise((resolve, reject) => {
+ onPopupEvent("popupshown", function() {
+ resolve(this);
+ });
+ });
+ yield ContentTask.spawn(browser, null, function*() {
+ content.navigator.geolocation.getCurrentPosition(function (pos) {
+ // Do nothing
+ });
+ });
+ let panel = yield popupShownPromise;
+ let notification = panel.children[0];
+ let body = document.getAnonymousElementByAttribute(notification,
+ "class",
+ "popup-notification-body");
+ ok(body.innerHTML.includes("example.com"), "Check that at least the eTLD+1 is present in the markup");
+ });
+});
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification.js b/browser/base/content/test/popupNotifications/browser_popupNotification.js
new file mode 100644
index 000000000..6be3e4205
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification.js
@@ -0,0 +1,203 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// These are shared between test #4 to #5
+var wrongBrowserNotificationObject = new BasicNotification("wrongBrowser");
+var wrongBrowserNotification;
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ { id: "Test#1",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.mainActionClicked, "mainAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ { id: "Test#2",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ { id: "Test#3",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // test opening a notification for a background browser
+ // Note: test 4 to 6 share a tab.
+ { id: "Test#4",
+ run: function* () {
+ let tab = gBrowser.addTab("about:blank");
+ isnot(gBrowser.selectedTab, tab, "new tab isn't selected");
+ wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(tab);
+ let promiseTopic = promiseTopicObserved("PopupNotifications-backgroundShow");
+ wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
+ yield promiseTopic;
+ is(PopupNotifications.isPanelOpen, false, "panel isn't open");
+ ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
+ ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
+ ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called");
+ goNext();
+ }
+ },
+ // now select that browser and test to see that the notification appeared
+ { id: "Test#5",
+ run: function () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
+ },
+ onShown: function (popup) {
+ checkPopup(popup, wrongBrowserNotificationObject);
+ is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie");
+
+ // switch back to the old browser
+ gBrowser.selectedTab = this.oldSelectedTab;
+ },
+ onHidden: function (popup) {
+ // actually remove the notification to prevent it from reappearing
+ ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch");
+ wrongBrowserNotification.remove();
+ ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered");
+ wrongBrowserNotification = null;
+ }
+ },
+ // test that the removed notification isn't shown on browser re-select
+ { id: "Test#6",
+ run: function* () {
+ let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
+ gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
+ yield promiseTopic;
+ is(PopupNotifications.isPanelOpen, false, "panel isn't open");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ goNext();
+ }
+ },
+ // Test that two notifications with the same ID result in a single displayed
+ // notification.
+ { id: "Test#7",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ // Show the same notification twice
+ this.notification1 = showNotification(this.notifyObj);
+ this.notification2 = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ this.notification2.remove();
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that two notifications with different IDs are displayed
+ { id: "Test#8",
+ run: function () {
+ this.testNotif1 = new BasicNotification(this.id);
+ this.testNotif1.message += " 1";
+ showNotification(this.testNotif1);
+ this.testNotif2 = new BasicNotification(this.id);
+ this.testNotif2.message += " 2";
+ this.testNotif2.id += "-2";
+ showNotification(this.testNotif2);
+ },
+ onShown: function (popup) {
+ is(popup.childNodes.length, 2, "two notifications are shown");
+ // Trigger the main command for the first notification, and the secondary
+ // for the second. Need to do mainCommand first since the secondaryCommand
+ // triggering is async.
+ triggerMainCommand(popup);
+ is(popup.childNodes.length, 1, "only one notification left");
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif1.mainActionClicked, "main action #1 was clicked");
+ ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked");
+ ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called");
+
+ ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked");
+ ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked");
+ ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called");
+ }
+ },
+ // Test notification without mainAction
+ { id: "Test#9",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.mainAction = null;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notification.remove();
+ }
+ },
+ // Test two notifications with different anchors
+ { id: "Test#10",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.firstNotification = showNotification(this.notifyObj);
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "-2";
+ this.notifyObj2.anchorID = "addons-notification-icon";
+ // Second showNotification() overrides the first
+ this.secondNotification = showNotification(this.notifyObj2);
+ },
+ onShown: function (popup) {
+ // This also checks that only one element is shown.
+ checkPopup(popup, this.notifyObj2);
+ is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor shouldn't be visible");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ // Remove the notifications
+ this.firstNotification.remove();
+ this.secondNotification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
new file mode 100644
index 000000000..d77098895
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ // Test optional params
+ { id: "Test#1",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.secondaryActions = undefined;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that icons appear
+ { id: "Test#2",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.id = "geolocation";
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ let icon = document.getElementById("geo-notification-icon");
+ isnot(icon.boxObject.width, 0,
+ "geo anchor should be visible after dismissal");
+ this.notification.remove();
+ is(icon.boxObject.width, 0,
+ "geo anchor should not be visible after removal");
+ }
+ },
+
+ // Test that persistence allows the notification to persist across reloads
+ { id: "Test#3",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ persistence: 2
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/")
+ // Next load will remove the notification
+ this.complete = true;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after 3 page loads");
+ ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that a timeout allows the notification to persist across reloads
+ { id: "Test#4",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ // Set a timeout of 10 minutes that should never be hit
+ this.notifyObj.addOptions({
+ timeout: Date.now() + 600000
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ // Next load will hide the notification
+ this.notification.options.timeout = Date.now() - 1;
+ this.complete = true;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after the timeout was passed");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that setting persistWhileVisible allows a visible notification to
+ // persist across location changes
+ { id: "Test#5",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ persistWhileVisible: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ // Notification should persist across location changes
+ this.complete = true;
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after it was dismissed");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+
+ // Test that nested icon nodes correctly activate popups
+ { id: "Test#6",
+ run: function() {
+ // Add a temporary box as the anchor with a button
+ this.box = document.createElement("box");
+ PopupNotifications.iconBox.appendChild(this.box);
+
+ let button = document.createElement("button");
+ button.setAttribute("label", "Please click me!");
+ this.box.appendChild(button);
+
+ // The notification should open up on the box
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = this.box.id = "nested-box";
+ this.notifyObj.addOptions({dismissed: true});
+ this.notification = showNotification(this.notifyObj);
+
+ // This test places a normal button in the notification area, which has
+ // standard GTK styling and dimensions. Due to the clip-path, this button
+ // gets clipped off, which makes it necessary to synthesize the mouse click
+ // a little bit downward. To be safe, I adjusted the x-offset with the same
+ // amount.
+ EventUtils.synthesizeMouse(button, 4, 4, {});
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification.remove();
+ this.box.parentNode.removeChild(this.box);
+ }
+ },
+ // Test that popupnotifications without popups have anchor icons shown
+ { id: "Test#7",
+ run: function* () {
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.anchorID = "geo-notification-icon";
+ notifyObj.addOptions({neverShow: true});
+ let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
+ showNotification(notifyObj);
+ yield promiseTopic;
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+ goNext();
+ }
+ },
+ // Test notification "Not Now" menu item
+ { id: "Test#8",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 1);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification close button
+ { id: "Test#9",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ EventUtils.synthesizeMouseAtCenter(notification.closebutton, {});
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification when chrome is hidden
+ { id: "Test#10",
+ run: function () {
+ window.locationbar.visible = false;
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ window.locationbar.visible = true;
+ }
+ },
+ // Test that dismissed popupnotifications can be opened on about:blank
+ // (where the rest of the identity block is disabled)
+ { id: "Test#11",
+ run: function() {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notifyObj.addOptions({dismissed: true});
+ this.notification = showNotification(this.notifyObj);
+
+ EventUtils.synthesizeMouse(document.getElementById("geo-notification-icon"), 0, 0, {});
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_3.js b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
new file mode 100644
index 000000000..33ec3f714
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
@@ -0,0 +1,305 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ // Test notification is removed when dismissed if removeOnDismissal is true
+ { id: "Test#1",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ removeOnDismissal: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test multiple notification icons are shown
+ { id: "Test#2",
+ run: function () {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notification1 = showNotification(this.notifyObj1);
+
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notification2 = showNotification(this.notifyObj2);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj2);
+
+ // check notifyObj1 anchor icon is showing
+ isnot(document.getElementById("default-notification-icon").boxObject.width, 0,
+ "default anchor should be visible");
+ // check notifyObj2 anchor icon is showing
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notification1.remove();
+ ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered");
+
+ this.notification2.remove();
+ ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that multiple notification icons are removed when switching tabs
+ { id: "Test#3",
+ run: function () {
+ // show the notification on old tab.
+ this.notifyObjOld = new BasicNotification(this.id);
+ this.notifyObjOld.anchorID = "default-notification-icon";
+ this.notificationOld = showNotification(this.notifyObjOld);
+
+ // switch tab
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // show the notification on new tab.
+ this.notifyObjNew = new BasicNotification(this.id);
+ this.notifyObjNew.anchorID = "geo-notification-icon";
+ this.notificationNew = showNotification(this.notifyObjNew);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObjNew);
+
+ // check notifyObjOld anchor icon is removed
+ is(document.getElementById("default-notification-icon").boxObject.width, 0,
+ "default anchor shouldn't be visible");
+ // check notifyObjNew anchor icon is showing
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notificationNew.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ gBrowser.selectedTab = this.oldSelectedTab;
+ this.notificationOld.remove();
+ }
+ },
+ // test security delay - too early
+ { id: "Test#4",
+ run: function () {
+ // Set the security delay to 100s
+ PopupNotifications.buttonDelay = 100000;
+
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+
+ // Wait to see if the main command worked
+ executeSoon(function delayedDismissal() {
+ dismissNotification(popup);
+ });
+
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon");
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ }
+ },
+ // test security delay - after delay
+ { id: "Test#5",
+ run: function () {
+ // Set the security delay to 10ms
+ PopupNotifications.buttonDelay = 10;
+
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+
+ // Wait until after the delay to trigger the main action
+ setTimeout(function delayedDismissal() {
+ triggerMainCommand(popup);
+ }, 500);
+
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered");
+ PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
+ }
+ },
+ // reload removes notification
+ { id: "Test#6",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ goNext();
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ gBrowser.selectedBrowser.reload();
+ });
+ }
+ },
+ // location change in background tab removes notification
+ { id: "Test#7",
+ run: function* () {
+ let oldSelectedTab = gBrowser.selectedTab;
+ let newTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = newTab;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ gBrowser.selectedTab = oldSelectedTab;
+ let browser = gBrowser.getBrowserForTab(newTab);
+
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.browser = browser;
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ executeSoon(function () {
+ gBrowser.removeTab(newTab);
+ goNext();
+ });
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ browser.reload();
+ });
+ }
+ },
+ // Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
+ { id: "Test#8",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ let originalTab = gBrowser.selectedTab;
+ let bgTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = bgTab;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ let anchor = document.createElement("box");
+ anchor.id = "test26-anchor";
+ anchor.className = "notification-anchor-icon";
+ PopupNotifications.iconBox.appendChild(anchor);
+
+ gBrowser.selectedTab = originalTab;
+
+ let fgNotifyObj = new BasicNotification(this.id);
+ fgNotifyObj.anchorID = anchor.id;
+ fgNotifyObj.options.dismissed = true;
+ let fgNotification = showNotification(fgNotifyObj);
+
+ let bgNotifyObj = new BasicNotification(this.id);
+ bgNotifyObj.anchorID = anchor.id;
+ bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab);
+ // show the notification in the background tab ...
+ let bgNotification = showNotification(bgNotifyObj);
+ // ... and re-show it
+ bgNotification = showNotification(bgNotifyObj);
+
+ ok(fgNotification.id, "notification has id");
+ is(fgNotification.id, bgNotification.id, "notification ids are the same");
+ is(anchor.getAttribute("showing"), "true", "anchor still showing");
+
+ fgNotification.remove();
+ gBrowser.removeTab(bgTab);
+ goNext();
+ }
+ },
+ // location change in an embedded frame should not remove a notification
+ { id: "Test#9",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html;charset=utf8,<iframe%20id='iframe'%20src='http://example.com/'>");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(false, "Notification removed from browser when subframe navigated");
+ }
+ };
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ let self = this;
+ let progressListener = {
+ onLocationChange: function onLocationChange() {
+ gBrowser.removeProgressListener(progressListener);
+
+ executeSoon(() => {
+ let notification = PopupNotifications.getNotification(self.notifyObj.id,
+ self.notifyObj.browser);
+ ok(notification != null, "Notification remained when subframe navigated");
+ self.notifyObj.options.eventCallback = undefined;
+
+ notification.remove();
+ });
+ },
+ };
+
+ info("Adding progress listener and performing navigation");
+ gBrowser.addProgressListener(progressListener);
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+ content.document.getElementById("iframe")
+ .setAttribute("src", "http://example.org/");
+ });
+ },
+ onHidden: function () {}
+ },
+ // Popup Notifications should catch exceptions from callbacks
+ { id: "Test#10",
+ run: function () {
+ this.testNotif1 = new BasicNotification(this.id);
+ this.testNotif1.message += " 1";
+ this.notification1 = showNotification(this.testNotif1);
+ this.testNotif1.options.eventCallback = function (eventName) {
+ info("notifyObj1.options.eventCallback: " + eventName);
+ if (eventName == "dismissed") {
+ throw new Error("Oops 1!");
+ }
+ };
+
+ this.testNotif2 = new BasicNotification(this.id);
+ this.testNotif2.message += " 2";
+ this.testNotif2.id += "-2";
+ this.testNotif2.options.eventCallback = function (eventName) {
+ info("notifyObj2.options.eventCallback: " + eventName);
+ if (eventName == "dismissed") {
+ throw new Error("Oops 2!");
+ }
+ };
+ this.notification2 = showNotification(this.testNotif2);
+ },
+ onShown: function (popup) {
+ is(popup.childNodes.length, 2, "two notifications are shown");
+ dismissNotification(popup);
+ },
+ onHidden: function () {
+ this.notification1.remove();
+ this.notification2.remove();
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
new file mode 100644
index 000000000..750ad82fd
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
@@ -0,0 +1,294 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ // Popup Notifications main actions should catch exceptions from callbacks
+ { id: "Test#1",
+ run: function () {
+ this.testNotif = new ErrorNotification();
+ showNotification(this.testNotif);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.testNotif);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif.mainActionClicked, "main action has been triggered");
+ }
+ },
+ // Popup Notifications secondary actions should catch exceptions from callbacks
+ { id: "Test#2",
+ run: function () {
+ this.testNotif = new ErrorNotification();
+ showNotification(this.testNotif);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.testNotif);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered");
+ }
+ },
+ // Existing popup notification shouldn't disappear when adding a dismissed notification
+ { id: "Test#3",
+ run: function () {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notification1 = showNotification(this.notifyObj1);
+ },
+ onShown: function (popup) {
+ // Now show a dismissed notification, and check that it doesn't clobber
+ // the showing one.
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notifyObj2.options.dismissed = true;
+ this.notification2 = showNotification(this.notifyObj2);
+
+ checkPopup(popup, this.notifyObj1);
+
+ // check that both anchor icons are showing
+ is(document.getElementById("default-notification-icon").getAttribute("showing"), "true",
+ "notification1 anchor should be visible");
+ is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true",
+ "notification2 anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification1.remove();
+ this.notification2.remove();
+ }
+ },
+ // Showing should be able to modify the popup data
+ { id: "Test#4",
+ run: function() {
+ this.notifyObj = new BasicNotification(this.id);
+ let normalCallback = this.notifyObj.options.eventCallback;
+ this.notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "showing") {
+ this.mainAction.label = "Alternate Label";
+ }
+ normalCallback.call(this, eventName);
+ };
+ showNotification(this.notifyObj);
+ },
+ onShown: function(popup) {
+ // checkPopup checks for the matching label. Note that this assumes that
+ // this.notifyObj.mainAction is the same as notification.mainAction,
+ // which could be a problem if we ever decided to deep-copy.
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function() { }
+ },
+ // Moving a tab to a new window should remove non-swappable notifications.
+ { id: "Test#5",
+ run: function() {
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ let notifyObj = new BasicNotification(this.id);
+ showNotification(notifyObj);
+ let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ whenDelayedStartupFinished(win, function() {
+ let anchor = win.document.getElementById("default-notification-icon");
+ win.PopupNotifications._reshowNotifications(anchor);
+ ok(win.PopupNotifications.panel.childNodes.length == 0,
+ "no notification displayed in new window");
+ ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
+ ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
+ win.close();
+ goNext();
+ });
+ }
+ },
+ // Moving a tab to a new window should preserve swappable notifications.
+ { id: "Test#6",
+ run: function* () {
+ let originalBrowser = gBrowser.selectedBrowser;
+ let originalWindow = window;
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ let notifyObj = new BasicNotification(this.id);
+ let originalCallback = notifyObj.options.eventCallback;
+ notifyObj.options.eventCallback = function (eventName) {
+ originalCallback(eventName);
+ return eventName == "swapping";
+ };
+
+ let notification = showNotification(notifyObj);
+ let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ yield whenDelayedStartupFinished(win);
+
+ yield new Promise(resolve => {
+ let originalCallback = notification.options.eventCallback;
+ notification.options.eventCallback = function (eventName) {
+ originalCallback(eventName);
+ if (eventName == "shown") {
+ resolve();
+ }
+ };
+ info("Showing the notification again");
+ notification.reshow();
+ });
+
+ checkPopup(win.PopupNotifications.panel, notifyObj);
+ ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
+ yield BrowserTestUtils.closeWindow(win);
+
+ // These are the same checks that PopupNotifications.jsm makes before it
+ // allows a notification to open. Do not go to the next test until we are
+ // sure that its attempt to display a notification will not fail.
+ yield BrowserTestUtils.waitForCondition(() => originalBrowser.docShellIsActive,
+ "The browser should be active");
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ yield BrowserTestUtils.waitForCondition(() => fm.activeWindow == originalWindow,
+ "The window should be active")
+
+ goNext();
+ }
+ },
+ // the hideNotNow option
+ { id: "Test#7",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.hideNotNow = true;
+ this.notifyObj.mainAction.dismiss = true;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ // checkPopup verifies that the Not Now item is hidden, and that no separator is added.
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ this.notification.remove();
+ }
+ },
+ // the main action callback can keep the notification.
+ { id: "Test#8",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.mainAction.dismiss = true;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
+ this.notification.remove();
+ }
+ },
+ // a secondary action callback can keep the notification.
+ { id: "Test#9",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.secondaryActions[0].dismiss = true;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
+ this.notification.remove();
+ }
+ },
+ // returning true in the showing callback should dismiss the notification.
+ { id: "Test#10",
+ run: function() {
+ let notifyObj = new BasicNotification(this.id);
+ let originalCallback = notifyObj.options.eventCallback;
+ notifyObj.options.eventCallback = function (eventName) {
+ originalCallback(eventName);
+ return eventName == "showing";
+ };
+
+ let notification = showNotification(notifyObj);
+ ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered");
+ ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered");
+ notification.remove();
+ goNext();
+ }
+ },
+ // panel updates should fire the showing and shown callbacks again.
+ { id: "Test#11",
+ run: function() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+
+ this.notifyObj.showingCallbackTriggered = false;
+ this.notifyObj.shownCallbackTriggered = false;
+
+ // Force an update of the panel. This is typically called
+ // automatically when receiving 'activate' or 'TabSelect' events,
+ // but from a setTimeout, which is inconvenient for the test.
+ PopupNotifications._update();
+
+ checkPopup(popup, this.notifyObj);
+
+ this.notification.remove();
+ },
+ onHidden: function() { }
+ },
+ // A first dismissed notification shouldn't stop _update from showing a second notification
+ { id: "Test#12",
+ run: function () {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notifyObj1.options.dismissed = true;
+ this.notification1 = showNotification(this.notifyObj1);
+
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notifyObj2.options.dismissed = true;
+ this.notification2 = showNotification(this.notifyObj2);
+
+ this.notification2.dismissed = false;
+ PopupNotifications._update();
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj2);
+ this.notification1.remove();
+ this.notification2.remove();
+ },
+ onHidden: function(popup) { }
+ },
+ // The anchor icon should be shown for notifications in background windows.
+ { id: "Test#13",
+ run: function() {
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.options.dismissed = true;
+ let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
+ whenDelayedStartupFinished(win, function() {
+ showNotification(notifyObj);
+ let anchor = document.getElementById("default-notification-icon");
+ is(anchor.getAttribute("showing"), "true", "the anchor is shown");
+ win.close();
+ goNext();
+ });
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
new file mode 100644
index 000000000..bcc51fcd7
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
@@ -0,0 +1,211 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+function checkCheckbox(checkbox, label, checked=false, hidden=false) {
+ is(checkbox.label, label, "Checkbox should have the correct label");
+ is(checkbox.hidden, hidden, "Checkbox should be shown");
+ is(checkbox.checked, checked, "Checkbox should be checked by default");
+}
+
+function checkMainAction(notification, disabled=false) {
+ let mainAction = notification.button;
+ let warningLabel = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-warning");
+ is(warningLabel.hidden, !disabled, "Warning label should be shown");
+ is(mainAction.disabled, disabled, "MainAction should be disabled");
+}
+
+function promiseElementVisible(element) {
+ // HTMLElement.offsetParent is null when the element is not visisble
+ // (or if the element has |position: fixed|). See:
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
+ return BrowserTestUtils.waitForCondition(() => element.offsetParent !== null,
+ "Waiting for element to be visible");
+}
+
+var gNotification;
+
+var tests = [
+ // Test that passing the checkbox field shows the checkbox.
+ { id: "show_checkbox",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ checkCheckbox(notification.checkbox, "This is a checkbox");
+ triggerMainCommand(popup);
+ },
+ onHidden: function () { }
+ },
+
+ // Test checkbox being checked by default
+ { id: "checkbox_checked",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "Check this",
+ checked: true,
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ checkCheckbox(notification.checkbox, "Check this", true);
+ triggerMainCommand(popup);
+ },
+ onHidden: function () { }
+ },
+
+ // Test checkbox passing the checkbox state on mainAction
+ { id: "checkbox_passCheckboxChecked_mainAction",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.mainAction.callback = ({checkboxChecked}) => this.mainActionChecked = checkboxChecked;
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox");
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", true);
+ triggerMainCommand(popup);
+ },
+ onHidden: function () {
+ is(this.mainActionChecked, true, "mainAction callback is passed the correct checkbox value");
+ }
+ },
+
+ // Test checkbox passing the checkbox state on secondaryAction
+ { id: "checkbox_passCheckboxChecked_secondaryAction",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.secondaryActions = [{
+ label: "Test Secondary",
+ accessKey: "T",
+ callback: ({checkboxChecked}) => this.secondaryActionChecked = checkboxChecked,
+ }];
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox");
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", true);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function () {
+ is(this.secondaryActionChecked, true, "secondaryAction callback is passed the correct checkbox value");
+ }
+ },
+
+ // Test checkbox preserving its state through re-opening the doorhanger
+ { id: "checkbox_reopen",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ checkedState: {
+ disableMainAction: true,
+ warningLabel: "Testing disable",
+ },
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox");
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ dismissNotification(popup);
+ },
+ onHidden: function* (popup) {
+ let icon = document.getElementById("default-notification-icon");
+ let shown = waitForNotificationPanel();
+ EventUtils.synthesizeMouseAtCenter(icon, {});
+ yield shown;
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox", true);
+ checkMainAction(notification, true);
+ gNotification.remove();
+ }
+ },
+];
+
+// Test checkbox disabling the main action in different combinations
+["checkedState", "uncheckedState"].forEach(function (state) {
+ [true, false].forEach(function (checked) {
+ tests.push(
+ { id: `checkbox_disableMainAction_${state}_${checked ? 'checked' : 'unchecked'}`,
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ checked: checked,
+ [state]: {
+ disableMainAction: true,
+ warningLabel: "Testing disable",
+ },
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ let disabled = (state === "checkedState" && checked) ||
+ (state === "uncheckedState" && !checked);
+
+ checkCheckbox(checkbox, "This is a checkbox", checked);
+ checkMainAction(notification, disabled);
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", !checked);
+ checkMainAction(notification, !disabled);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", checked);
+ checkMainAction(notification, disabled);
+
+ // Unblock the main command if it's currently disabled.
+ if (disabled) {
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ }
+ triggerMainCommand(popup);
+ },
+ onHidden: function () { }
+ }
+ );
+ });
+});
+
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
new file mode 100644
index 000000000..0f5b57ced
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+}
+
+var tests = [
+ // Test that for persistent notifications,
+ // the secondary action is triggered by pressing the escape key.
+ { id: "Test#1",
+ run() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.persistent = true;
+ showNotification(this.notifyObj);
+ },
+ onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ },
+ onHidden(popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked");
+ ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that for non-persistent notifications, the escape key dismisses the notification.
+ { id: "Test#2",
+ *run() {
+ yield waitForWindowReadyForPopupNotifications(window);
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ },
+ onHidden(popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked");
+ ok(!this.notifyObj.secondaryActionClicked, "secondaryAction was not clicked");
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered");
+ this.notification.remove();
+ }
+ },
+ // Test that the space key on an anchor element focuses an active notification
+ { id: "Test#3",
+ *run() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notifyObj.addOptions({
+ persistent: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ *onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ let anchor = document.getElementById(this.notifyObj.anchorID);
+ anchor.focus();
+ is(document.activeElement, anchor);
+ EventUtils.synthesizeKey(" ", {});
+ is(document.activeElement, popup.childNodes[0].button);
+ this.notification.remove();
+ },
+ onHidden(popup) { }
+ },
+];
diff --git a/browser/base/content/test/popupNotifications/browser_reshow_in_background.js b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js
new file mode 100644
index 000000000..6f415f62e
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js
@@ -0,0 +1,52 @@
+"use strict";
+
+/**
+ * Tests that when PopupNotifications for background tabs are reshown, they
+ * don't show up in the foreground tab, but only in the background tab that
+ * they belong to.
+ */
+add_task(function* test_background_notifications_dont_reshow_in_foreground() {
+ // Our initial tab will be A. Let's open two more tabs B and C, but keep
+ // A selected. Then, we'll trigger a PopupNotification in C, and then make
+ // it reshow.
+ let tabB = gBrowser.addTab("about:blank");
+ let tabC = gBrowser.addTab("about:blank");
+
+ let seenEvents = [];
+
+ let options = {
+ dismissed: false,
+ eventCallback(popupEvent) {
+ seenEvents.push(popupEvent);
+ },
+ };
+
+ let notification =
+ PopupNotifications.show(tabC.linkedBrowser, "test-notification",
+ "", "plugins-notification-icon",
+ null, null, options);
+ Assert.deepEqual(seenEvents, [], "Should have seen no events yet.");
+
+ yield BrowserTestUtils.switchTab(gBrowser, tabB);
+ Assert.deepEqual(seenEvents, [], "Should have seen no events yet.");
+
+ notification.reshow();
+ Assert.deepEqual(seenEvents, [], "Should have seen no events yet.");
+
+ let panelShown =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ yield BrowserTestUtils.switchTab(gBrowser, tabC);
+ yield panelShown;
+
+ Assert.equal(seenEvents.length, 2, "Should have seen two events.");
+ Assert.equal(seenEvents[0], "showing", "Should have said popup was showing.");
+ Assert.equal(seenEvents[1], "shown", "Should have said popup was shown.");
+
+ let panelHidden =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ PopupNotifications.remove(notification);
+ yield panelHidden;
+
+ yield BrowserTestUtils.removeTab(tabB);
+ yield BrowserTestUtils.removeTab(tabC);
+});
diff --git a/browser/base/content/test/popupNotifications/head.js b/browser/base/content/test/popupNotifications/head.js
new file mode 100644
index 000000000..4a803d6af
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -0,0 +1,303 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ return new Promise(resolve => {
+ info("Waiting for delayed startup to finish");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ if (aCallback) {
+ executeSoon(aCallback);
+ }
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+/**
+ * Allows waiting for an observer notification once.
+ *
+ * @param topic
+ * Notification topic to observe.
+ *
+ * @return {Promise}
+ * @resolves The array [subject, data] from the observed notification.
+ * @rejects Never.
+ */
+function promiseTopicObserved(topic)
+{
+ let deferred = Promise.defer();
+ info("Waiting for observer topic " + topic);
+ Services.obs.addObserver(function PTO_observe(subject, topic, data) {
+ Services.obs.removeObserver(PTO_observe, topic);
+ deferred.resolve([subject, data]);
+ }, topic, false);
+ return deferred.promise;
+}
+
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url)
+{
+ let browser = tab.linkedBrowser;
+
+ if (url) {
+ browser.loadURI(url);
+ }
+
+ return BrowserTestUtils.browserLoaded(browser, false, url);
+}
+
+const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
+
+function setup() {
+ // Disable transitions as they slow the test down and we want to click the
+ // mouse buttons in a predictable location.
+
+ registerCleanupFunction(() => {
+ PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
+ });
+}
+
+function goNext() {
+ executeSoon(() => executeSoon(Task.async(runNextTest)));
+}
+
+function* runNextTest() {
+ if (tests.length == 0) {
+ executeSoon(finish);
+ return;
+ }
+
+ let nextTest = tests.shift();
+ if (nextTest.onShown) {
+ let shownState = false;
+ onPopupEvent("popupshowing", function () {
+ info("[" + nextTest.id + "] popup showing");
+ });
+ onPopupEvent("popupshown", function () {
+ shownState = true;
+ info("[" + nextTest.id + "] popup shown");
+ Task.spawn(() => nextTest.onShown(this))
+ .then(undefined, ex => Assert.ok(false, "onShown failed: " + ex));
+ });
+ onPopupEvent("popuphidden", function () {
+ info("[" + nextTest.id + "] popup hidden");
+ Task.spawn(() => nextTest.onHidden(this))
+ .then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex));
+ }, () => shownState);
+ info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen);
+ }
+
+ info("[" + nextTest.id + "] running test");
+ yield nextTest.run();
+}
+
+function showNotification(notifyObj) {
+ info("Showing notification " + notifyObj.id);
+ return PopupNotifications.show(notifyObj.browser,
+ notifyObj.id,
+ notifyObj.message,
+ notifyObj.anchorID,
+ notifyObj.mainAction,
+ notifyObj.secondaryActions,
+ notifyObj.options);
+}
+
+function dismissNotification(popup) {
+ info("Dismissing notification " + popup.childNodes[0].id);
+ executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
+}
+
+function BasicNotification(testId) {
+ this.browser = gBrowser.selectedBrowser;
+ this.id = "test-notification-" + testId;
+ this.message = "This is popup notification for " + testId;
+ this.anchorID = null;
+ this.mainAction = {
+ label: "Main Action",
+ accessKey: "M",
+ callback: () => this.mainActionClicked = true
+ };
+ this.secondaryActions = [
+ {
+ label: "Secondary Action",
+ accessKey: "S",
+ callback: () => this.secondaryActionClicked = true
+ }
+ ];
+ this.options = {
+ eventCallback: eventName => {
+ switch (eventName) {
+ case "dismissed":
+ this.dismissalCallbackTriggered = true;
+ break;
+ case "showing":
+ this.showingCallbackTriggered = true;
+ break;
+ case "shown":
+ this.shownCallbackTriggered = true;
+ break;
+ case "removed":
+ this.removedCallbackTriggered = true;
+ break;
+ case "swapping":
+ this.swappingCallbackTriggered = true;
+ break;
+ }
+ }
+ };
+}
+
+BasicNotification.prototype.addOptions = function(options) {
+ for (let [name, value] of Object.entries(options))
+ this.options[name] = value;
+};
+
+function ErrorNotification() {
+ this.mainAction.callback = () => {
+ this.mainActionClicked = true;
+ throw new Error("Oops!");
+ };
+ this.secondaryActions[0].callback = () => {
+ this.secondaryActionClicked = true;
+ throw new Error("Oops!");
+ };
+}
+
+ErrorNotification.prototype = new BasicNotification();
+ErrorNotification.prototype.constructor = ErrorNotification;
+
+function checkPopup(popup, notifyObj) {
+ info("Checking notification " + notifyObj.id);
+
+ ok(notifyObj.showingCallbackTriggered, "showing callback was triggered");
+ ok(notifyObj.shownCallbackTriggered, "shown callback was triggered");
+
+ let notifications = popup.childNodes;
+ is(notifications.length, 1, "one notification displayed");
+ let notification = notifications[0];
+ if (!notification)
+ return;
+ let icon = document.getAnonymousElementByAttribute(notification, "class",
+ "popup-notification-icon");
+ if (notifyObj.id == "geolocation") {
+ isnot(icon.boxObject.width, 0, "icon for geo displayed");
+ ok(popup.anchorNode.classList.contains("notification-anchor-icon"),
+ "notification anchored to icon");
+ }
+ is(notification.getAttribute("label"), notifyObj.message, "message matches");
+ is(notification.id, notifyObj.id + "-notification", "id matches");
+ if (notifyObj.mainAction) {
+ is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
+ "main action label matches");
+ is(notification.getAttribute("buttonaccesskey"),
+ notifyObj.mainAction.accessKey, "main action accesskey matches");
+ }
+ let actualSecondaryActions =
+ Array.filter(notification.childNodes, child => child.nodeName == "menuitem");
+ let secondaryActions = notifyObj.secondaryActions || [];
+ let actualSecondaryActionsCount = actualSecondaryActions.length;
+ if (notifyObj.options.hideNotNow) {
+ is(notification.getAttribute("hidenotnow"), "true", "'Not Now' item hidden");
+ if (secondaryActions.length)
+ is(notification.lastChild.tagName, "menuitem", "no menuseparator");
+ }
+ else if (secondaryActions.length) {
+ is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
+ }
+ is(actualSecondaryActionsCount, secondaryActions.length,
+ actualSecondaryActions.length + " secondary actions");
+ secondaryActions.forEach(function (a, i) {
+ is(actualSecondaryActions[i].getAttribute("label"), a.label,
+ "label for secondary action " + i + " matches");
+ is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey,
+ "accessKey for secondary action " + i + " matches");
+ });
+}
+
+XPCOMUtils.defineLazyGetter(this, "gActiveListeners", () => {
+ let listeners = new Map();
+ registerCleanupFunction(() => {
+ for (let [listener, eventName] of listeners) {
+ PopupNotifications.panel.removeEventListener(eventName, listener, false);
+ }
+ });
+ return listeners;
+});
+
+function onPopupEvent(eventName, callback, condition) {
+ let listener = event => {
+ if (event.target != PopupNotifications.panel ||
+ (condition && !condition()))
+ return;
+ PopupNotifications.panel.removeEventListener(eventName, listener, false);
+ gActiveListeners.delete(listener);
+ executeSoon(() => callback.call(PopupNotifications.panel));
+ }
+ gActiveListeners.set(listener, eventName);
+ PopupNotifications.panel.addEventListener(eventName, listener, false);
+}
+
+function waitForNotificationPanel() {
+ return new Promise(resolve => {
+ onPopupEvent("popupshown", function() {
+ resolve(this);
+ });
+ });
+}
+
+function triggerMainCommand(popup) {
+ let notifications = popup.childNodes;
+ ok(notifications.length > 0, "at least one notification displayed");
+ let notification = notifications[0];
+ info("Triggering main command for notification " + notification.id);
+ // 20, 10 so that the inner button is hit
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+}
+
+function triggerSecondaryCommand(popup, index) {
+ let notifications = popup.childNodes;
+ ok(notifications.length > 0, "at least one notification displayed");
+ let notification = notifications[0];
+ info("Triggering secondary command for notification " + notification.id);
+ // Cancel the arrow panel slide-in transition (bug 767133) such that
+ // it won't interfere with us interacting with the dropdown.
+ document.getAnonymousNodes(popup)[0].style.transition = "none";
+
+ notification.button.focus();
+
+ popup.addEventListener("popupshown", function handle() {
+ popup.removeEventListener("popupshown", handle, false);
+ info("Command popup open for notification " + notification.id);
+ // Press down until the desired command is selected
+ for (let i = 0; i <= index; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ }
+ // Activate
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ // One down event to open the popup
+ info("Open the popup to trigger secondary command for notification " + notification.id);
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.includes("Mac") });
+}
diff --git a/browser/base/content/test/popups/browser.ini b/browser/base/content/test/popups/browser.ini
new file mode 100644
index 000000000..46a32783b
--- /dev/null
+++ b/browser/base/content/test/popups/browser.ini
@@ -0,0 +1,4 @@
+[browser_popupUI.js]
+[browser_popup_blocker.js]
+support-files = popup_blocker.html
+skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
diff --git a/browser/base/content/test/popups/browser_popupUI.js b/browser/base/content/test/popups/browser_popupUI.js
new file mode 100644
index 000000000..7c6805f60
--- /dev/null
+++ b/browser/base/content/test/popups/browser_popupUI.js
@@ -0,0 +1,37 @@
+function test() {
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({ set: [[ "dom.disable_open_during_load", false ]] });
+
+ let popupOpened = BrowserTestUtils.waitForNewWindow(true, "about:blank");
+ BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "data:text/html,<html><script>popup=open('about:blank','','width=300,height=200')</script>"
+ );
+ popupOpened.then((win) => testPopupUI(win));
+}
+
+function testPopupUI(win) {
+ var doc = win.document;
+
+ ok(win.gURLBar, "location bar exists in the popup");
+ isnot(win.gURLBar.clientWidth, 0, "location bar is visible in the popup");
+ ok(win.gURLBar.readOnly, "location bar is read-only in the popup");
+ isnot(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true",
+ "'open location' command is not disabled in the popup");
+
+ let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid",
+ "historydropmarker");
+ is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup");
+
+ EventUtils.synthesizeKey("t", { accelKey: true }, win);
+ is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup");
+ is(gBrowser.browsers.length, 3, "Accel+T opened a new tab in the parent window");
+ gBrowser.removeCurrentTab();
+
+ EventUtils.synthesizeKey("w", { accelKey: true }, win);
+ ok(win.closed, "Accel+W closes the popup");
+
+ if (!win.closed)
+ win.close();
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/popups/browser_popup_blocker.js b/browser/base/content/test/popups/browser_popup_blocker.js
new file mode 100644
index 000000000..8cadfea57
--- /dev/null
+++ b/browser/base/content/test/popups/browser_popup_blocker.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const baseURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+
+function clearAllPermissionsByPrefix(aPrefix) {
+ let perms = Services.perms.enumerator;
+ while (perms.hasMoreElements()) {
+ let perm = perms.getNext();
+ if (perm.type.startsWith(aPrefix)) {
+ Services.perms.removePermission(perm);
+ }
+ }
+}
+
+add_task(function* test_opening_blocked_popups() {
+ // Enable the popup blocker.
+ yield SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]});
+
+ // Open the test page.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, baseURL + "popup_blocker.html");
+
+ // Wait for the popup-blocked notification.
+ let notification;
+ yield BrowserTestUtils.waitForCondition(() =>
+ notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
+
+ // Show the menu.
+ let popupShown = BrowserTestUtils.waitForEvent(window, "popupshown");
+ let popupFilled = BrowserTestUtils.waitForMessage(gBrowser.selectedBrowser.messageManager,
+ "PopupBlocking:ReplyGetBlockedPopupList");
+ notification.querySelector("button").doCommand();
+ let popup_event = yield popupShown;
+ let menu = popup_event.target;
+ is(menu.id, "blockedPopupOptions", "Blocked popup menu shown");
+
+ yield popupFilled;
+ // The menu is filled on the same message that we waited for, so let's ensure that it
+ // had a chance of running before this test code.
+ yield new Promise(resolve => executeSoon(resolve));
+
+ // Check the menu contents.
+ let sep = menu.querySelector("menuseparator");
+ let popupCount = 0;
+ for (let i = sep.nextElementSibling; i; i = i.nextElementSibling) {
+ popupCount++;
+ }
+ is(popupCount, 2, "Two popups were blocked");
+
+ // Pressing "allow" should open all blocked popups.
+ let popupTabs = [];
+ function onTabOpen(event) {
+ popupTabs.push(event.target);
+ }
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen);
+
+ // Press the button.
+ let allow = menu.querySelector("[observes='blockedPopupAllowSite']");
+ allow.doCommand();
+ yield BrowserTestUtils.waitForCondition(() =>
+ popupTabs.length == 2 &&
+ popupTabs.every(aTab => aTab.linkedBrowser.currentURI.spec != "about:blank"));
+
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen);
+
+ is(popupTabs[0].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,a", "Popup a");
+ is(popupTabs[1].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,b", "Popup b");
+
+ // Clean up.
+ gBrowser.removeTab(tab);
+ for (let popup of popupTabs) {
+ gBrowser.removeTab(popup);
+ }
+ clearAllPermissionsByPrefix("popup");
+ // Ensure the menu closes.
+ menu.hidePopup();
+});
+
+add_task(function* check_icon_hides() {
+ // Enable the popup blocker.
+ yield SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]});
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, baseURL + "popup_blocker.html");
+
+ let button = document.getElementById("page-report-button");
+ yield BrowserTestUtils.waitForCondition(() =>
+ gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
+ ok(!button.hidden, "Button should be visible");
+
+ let otherPageLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ openLinkIn(baseURL, "current", {});
+ yield otherPageLoaded;
+ ok(button.hidden, "Button should have hidden again after another page loaded.");
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/popups/popup_blocker.html b/browser/base/content/test/popups/popup_blocker.html
new file mode 100644
index 000000000..6e2b7db15
--- /dev/null
+++ b/browser/base/content/test/popups/popup_blocker.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>Page creating two popups</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ window.open("data:text/plain;charset=utf-8,a", "a");
+ window.open("data:text/plain;charset=utf-8,b", "b");
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/referrer/.eslintrc.js b/browser/base/content/test/referrer/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/referrer/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/referrer/browser.ini b/browser/base/content/test/referrer/browser.ini
new file mode 100644
index 000000000..13b712850
--- /dev/null
+++ b/browser/base/content/test/referrer/browser.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+support-files =
+ file_referrer_policyserver.sjs
+ file_referrer_policyserver_attr.sjs
+ file_referrer_testserver.sjs
+ head.js
+
+[browser_referrer_middle_click.js]
+[browser_referrer_middle_click_in_container.js]
+[browser_referrer_open_link_in_private.js]
+skip-if = os == 'linux' # Bug 1145199
+[browser_referrer_open_link_in_tab.js]
+skip-if = os == 'linux' # Bug 1144816
+[browser_referrer_open_link_in_window.js]
+skip-if = os == 'linux' # Bug 1145199
+[browser_referrer_open_link_in_window_in_container.js]
+skip-if = os == 'linux' # Bug 1145199
+[browser_referrer_simple_click.js]
+[browser_referrer_open_link_in_container_tab.js]
+skip-if = os == 'linux' # Bug 1144816
+[browser_referrer_open_link_in_container_tab2.js]
+skip-if = os == 'linux' # Bug 1144816
+[browser_referrer_open_link_in_container_tab3.js]
+skip-if = os == 'linux' # Bug 1144816
diff --git a/browser/base/content/test/referrer/browser_referrer_middle_click.js b/browser/base/content/test/referrer/browser_referrer_middle_click.js
new file mode 100644
index 000000000..e6e01c6a3
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_middle_click.js
@@ -0,0 +1,20 @@
+// Tests referrer on middle-click navigation.
+// Middle-clicks on the link, which opens it in a new tab.
+
+function startMiddleClickTestCase(aTestNumber) {
+ info("browser_referrer_middle_click: " +
+ getReferrerTestDescription(aTestNumber));
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => {
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startMiddleClickTestCase);
+ });
+ });
+
+ clickTheLink(gTestWindow, "testlink", {button: 1});
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startMiddleClickTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js b/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js
new file mode 100644
index 000000000..e89b891f3
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js
@@ -0,0 +1,27 @@
+// Tests referrer on middle-click navigation.
+// Middle-clicks on the link, which opens it in a new tab, same container.
+
+function startMiddleClickTestCase(aTestNumber) {
+ info("browser_referrer_middle_click: " +
+ getReferrerTestDescription(aTestNumber));
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => {
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startMiddleClickTestCase,
+ { userContextId: 3 });
+ });
+ });
+
+ clickTheLink(gTestWindow, "testlink", {button: 1});
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startMiddleClickTestCase, { userContextId: 3 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js
new file mode 100644
index 000000000..deaf90fb9
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js
@@ -0,0 +1,59 @@
+// Tests referrer on context menu navigation - open link in new container tab.
+// Selects "open link in new container tab" from the context menu.
+
+function getReferrerTest(aTestNumber) {
+ let test = _referrerTests[aTestNumber];
+ if (test) {
+ // We want all the referrer tests to fail!
+ test.result = "";
+ }
+
+ return test;
+}
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_container_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase);
+ });
+
+ let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu");
+
+ let menupopup = menu.menupopup;
+ menu.addEventListener("popupshown", function onPopupShown() {
+ menu.removeEventListener("popupshown", onPopupShown);
+
+ is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup.");
+ ok(menupopup.firstChild, "We have a first container entry.");
+
+ let firstContext = menupopup.firstChild;
+ is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry.");
+ ok(firstContext.hasAttribute("data-usercontextid"), "We have a usercontextid value.");
+
+ aContextMenu.addEventListener("popuphidden", function onPopupHidden() {
+ aContextMenu.removeEventListener("popuphidden", onPopupHidden);
+ firstContext.doCommand();
+ });
+
+ aContextMenu.hidePopup();
+ });
+
+ menupopup.showPopup();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase);
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js
new file mode 100644
index 000000000..77a5645c6
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js
@@ -0,0 +1,31 @@
+// Tests referrer on context menu navigation - open link in new container tab.
+// Selects "open link in new container tab" from the context menu.
+
+// The test runs from a container ID 1.
+// Output: we have the correct referrer policy applied.
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_container_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase, { userContextId: 1 });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkincontainertab");
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase, { userContextId: 1 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js
new file mode 100644
index 000000000..c0a73d828
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js
@@ -0,0 +1,63 @@
+// Tests referrer on context menu navigation - open link in new container tab.
+// Selects "open link in new container tab" from the context menu.
+
+// The test runs from a container ID 2.
+// Output: we have no referrer.
+
+function getReferrerTest(aTestNumber) {
+ let test = _referrerTests[aTestNumber];
+ if (test) {
+ // We want all the referrer tests to fail!
+ test.result = "";
+ }
+
+ return test;
+}
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_container_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase, { userContextId: 2 });
+ });
+
+ let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu");
+
+ let menupopup = menu.menupopup;
+ menu.addEventListener("popupshown", function onPopupShown() {
+ menu.removeEventListener("popupshown", onPopupShown);
+
+ is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup.");
+ ok(menupopup.firstChild, "We have a first container entry.");
+
+ let firstContext = menupopup.firstChild;
+ is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry.");
+ ok(firstContext.hasAttribute("data-usercontextid"), "We have a usercontextid value.");
+ is("0", firstContext.getAttribute("data-usercontextid"), "We have the right usercontextid value.");
+
+ aContextMenu.addEventListener("popuphidden", function onPopupHidden() {
+ aContextMenu.removeEventListener("popuphidden", onPopupHidden);
+ firstContext.doCommand();
+ });
+
+ aContextMenu.hidePopup();
+ });
+
+ menupopup.showPopup();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase, { userContextId: 2 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
new file mode 100644
index 000000000..8f12e3824
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
@@ -0,0 +1,22 @@
+// Tests referrer on context menu navigation - open link in new private window.
+// Selects "open link in new private window" from the context menu.
+
+function startNewPrivateWindowTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_private: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ newWindowOpened().then(function(aNewWindow) {
+ BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+ startNewPrivateWindowTestCase);
+ });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkprivate");
+ });
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewPrivateWindowTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js
new file mode 100644
index 000000000..03119cb57
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js
@@ -0,0 +1,21 @@
+// Tests referrer on context menu navigation - open link in new tab.
+// Selects "open link in new tab" from the context menu.
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase);
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkintab");
+ });
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
new file mode 100644
index 000000000..81e7b2648
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
@@ -0,0 +1,22 @@
+// Tests referrer on context menu navigation - open link in new window.
+// Selects "open link in new window" from the context menu.
+
+function startNewWindowTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_window: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ newWindowOpened().then(function(aNewWindow) {
+ BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+ startNewWindowTestCase);
+ });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
+ });
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewWindowTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js
new file mode 100644
index 000000000..d5ce87952
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js
@@ -0,0 +1,32 @@
+// Tests referrer on context menu navigation - open link in new window.
+// Selects "open link in new window" from the context menu.
+
+// This test runs from a container tab. The new tab/window will be loaded in
+// the same container.
+
+function startNewWindowTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_window: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ newWindowOpened().then(function(aNewWindow) {
+ BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+ startNewWindowTestCase,
+ { userContextId: 1 });
+ });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewWindowTestCase, { userContextId: 1 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_simple_click.js b/browser/base/content/test/referrer/browser_referrer_simple_click.js
new file mode 100644
index 000000000..7f3784e64
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_simple_click.js
@@ -0,0 +1,20 @@
+// Tests referrer on simple click navigation.
+// Clicks on the link, which opens it in the same tab.
+
+function startSimpleClickTestCase(aTestNumber) {
+ info("browser_referrer_simple_click: " +
+ getReferrerTestDescription(aTestNumber));
+ BrowserTestUtils.browserLoaded(gTestWindow.gBrowser.selectedBrowser, false,
+ (url) => url.endsWith("file_referrer_testserver.sjs"))
+ .then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, null, null,
+ startSimpleClickTestCase);
+ });
+
+ clickTheLink(gTestWindow, "testlink", {});
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startSimpleClickTestCase);
+}
diff --git a/browser/base/content/test/referrer/file_referrer_policyserver.sjs b/browser/base/content/test/referrer/file_referrer_policyserver.sjs
new file mode 100644
index 000000000..e07965675
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_policyserver.sjs
@@ -0,0 +1,37 @@
+/**
+ * Renders a link with the provided referrer policy.
+ * Used in browser_referrer_*.js, bug 1113431.
+ * Arguments: ?scheme=http://&policy=origin&rel=noreferrer
+ */
+function handleRequest(request, response)
+{
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let scheme = query.get("scheme");
+ let policy = query.get("policy");
+ let rel = query.get("rel");
+
+ let linkUrl = scheme +
+ "test1.example.com/browser/browser/base/content/test/referrer/" +
+ "file_referrer_testserver.sjs";
+ let metaReferrerTag =
+ policy ? `<meta name='referrer' content='${policy}'>` : "";
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ ${metaReferrerTag}
+ <title>Test referrer</title>
+ </head>
+ <body>
+ <a id='testlink' href='${linkUrl}' ${rel ? ` rel='${rel}'` : ""}>
+ referrer test link</a>
+ </body>
+ </html>`;
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(html);
+}
diff --git a/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs b/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs
new file mode 100644
index 000000000..25a58188a
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs
@@ -0,0 +1,36 @@
+/**
+ * Renders a link with the provided referrer policy.
+ * Used in browser_referrer_*.js, bug 1113431.
+ * Arguments: ?scheme=http://&policy=origin&rel=noreferrer
+ */
+function handleRequest(request, response)
+{
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let scheme = query.get("scheme");
+ let policy = query.get("policy");
+ let rel = query.get("rel");
+
+ let linkUrl = scheme +
+ "test1.example.com/browser/browser/base/content/test/referrer/" +
+ "file_referrer_testserver.sjs";
+ let referrerPolicy =
+ policy ? `referrerpolicy="${policy}"` : "";
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Test referrer</title>
+ </head>
+ <body>
+ <a id='testlink' href='${linkUrl}' ${referrerPolicy} ${rel ? ` rel='${rel}'` : ""}>
+ referrer test link</a>
+ </body>
+ </html>`;
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(html);
+}
diff --git a/browser/base/content/test/referrer/file_referrer_testserver.sjs b/browser/base/content/test/referrer/file_referrer_testserver.sjs
new file mode 100644
index 000000000..0cfc53b2c
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_testserver.sjs
@@ -0,0 +1,31 @@
+/**
+ * Renders the HTTP Referer header up to the second path slash.
+ * Used in browser_referrer_*.js, bug 1113431.
+ */
+function handleRequest(request, response)
+{
+ let referrer = "";
+ try {
+ referrer = request.getHeader("referer");
+ } catch (e) {
+ referrer = "";
+ }
+
+ // Strip it past the first path slash. Makes tests easier to read.
+ referrer = referrer.split("/").slice(0, 4).join("/");
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Test referrer</title>
+ </head>
+ <body>
+ <div id='testdiv'>${referrer}</div>
+ </body>
+ </html>`;
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(html);
+}
diff --git a/browser/base/content/test/referrer/head.js b/browser/base/content/test/referrer/head.js
new file mode 100644
index 000000000..1a5d5b051
--- /dev/null
+++ b/browser/base/content/test/referrer/head.js
@@ -0,0 +1,265 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+ "resource://testing-common/BrowserTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentTask",
+ "resource://testing-common/ContentTask.jsm");
+
+const REFERRER_URL_BASE = "/browser/browser/base/content/test/referrer/";
+const REFERRER_POLICYSERVER_URL =
+ "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver.sjs";
+const REFERRER_POLICYSERVER_URL_ATTRIBUTE =
+ "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver_attr.sjs";
+
+SpecialPowers.pushPrefEnv({"set": [['network.http.enablePerElementReferrer', true]]});
+
+var gTestWindow = null;
+var rounds = 0;
+
+// We test that the UI code propagates three pieces of state - the referrer URI
+// itself, the referrer policy, and the triggering principal. After that, we
+// trust nsIWebNavigation to do the right thing with the info it's given, which
+// is covered more exhaustively by dom/base/test/test_bug704320.html (which is
+// a faster content-only test). So, here, we limit ourselves to cases that
+// would break when the UI code drops either of these pieces; we don't try to
+// duplicate the entire cross-product test in bug 704320 - that would be slow,
+// especially when we're opening a new window for each case.
+var _referrerTests = [
+ // 1. Normal cases - no referrer policy, no special attributes.
+ // We expect a full referrer normally, and no referrer on downgrade.
+ {
+ fromScheme: "http://",
+ toScheme: "http://",
+ result: "http://test1.example.com/browser" // full referrer
+ },
+ {
+ fromScheme: "https://",
+ toScheme: "http://",
+ result: "" // no referrer when downgrade
+ },
+ // 2. Origin referrer policy - we expect an origin referrer,
+ // even on downgrade. But rel=noreferrer trumps this.
+ {
+ fromScheme: "https://",
+ toScheme: "http://",
+ policy: "origin",
+ result: "https://test1.example.com/" // origin, even on downgrade
+ },
+ {
+ fromScheme: "https://",
+ toScheme: "http://",
+ policy: "origin",
+ rel: "noreferrer",
+ result: "" // rel=noreferrer trumps meta-referrer
+ },
+ // 3. XXX: using no-referrer here until we support all attribute values (bug 1178337)
+ // Origin-when-cross-origin policy - this depends on the triggering
+ // principal. We expect full referrer for same-origin requests,
+ // and origin referrer for cross-origin requests.
+ {
+ fromScheme: "https://",
+ toScheme: "https://",
+ policy: "no-referrer",
+ result: "" // same origin https://test1.example.com/browser
+ },
+ {
+ fromScheme: "http://",
+ toScheme: "https://",
+ policy: "no-referrer",
+ result: "" // cross origin http://test1.example.com
+ },
+];
+
+/**
+ * Returns the test object for a given test number.
+ * @param aTestNumber The test number - 0, 1, 2, ...
+ * @return The test object, or undefined if the number is out of range.
+ */
+function getReferrerTest(aTestNumber) {
+ return _referrerTests[aTestNumber];
+}
+
+/**
+ * Returns a brief summary of the test, for logging.
+ * @param aTestNumber The test number - 0, 1, 2...
+ * @return The test description.
+ */
+function getReferrerTestDescription(aTestNumber) {
+ let test = getReferrerTest(aTestNumber);
+ return "policy=[" + test.policy + "] " +
+ "rel=[" + test.rel + "] " +
+ test.fromScheme + " -> " + test.toScheme;
+}
+
+/**
+ * Clicks the link.
+ * @param aWindow The window to click the link in.
+ * @param aLinkId The id of the link element.
+ * @param aOptions The options for synthesizeMouseAtCenter.
+ */
+function clickTheLink(aWindow, aLinkId, aOptions) {
+ return BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + aLinkId, aOptions, aWindow.gBrowser.selectedBrowser);
+}
+
+/**
+ * Extracts the referrer result from the target window.
+ * @param aWindow The window where the referrer target has loaded.
+ * @return {Promise}
+ * @resolves When extacted, with the text of the (trimmed) referrer.
+ */
+function referrerResultExtracted(aWindow) {
+ return ContentTask.spawn(aWindow.gBrowser.selectedBrowser, {}, function() {
+ return content.document.getElementById("testdiv").textContent;
+ });
+}
+
+/**
+ * Waits for browser delayed startup to finish.
+ * @param aWindow The window to wait for.
+ * @return {Promise}
+ * @resolves When the window is loaded.
+ */
+function delayedStartupFinished(aWindow) {
+ return new Promise(function(resolve) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+/**
+ * Waits for some (any) tab to load. The caller triggers the load.
+ * @param aWindow The window where to wait for a tab to load.
+ * @return {Promise}
+ * @resolves With the tab once it's loaded.
+ */
+function someTabLoaded(aWindow) {
+ return BrowserTestUtils.waitForNewTab(gTestWindow.gBrowser).then((tab) => {
+ return BrowserTestUtils.browserStopped(tab.linkedBrowser).then(() => tab);
+ });
+}
+
+/**
+ * Waits for a new window to open and load. The caller triggers the open.
+ * @return {Promise}
+ * @resolves With the new window once it's open and loaded.
+ */
+function newWindowOpened() {
+ return TestUtils.topicObserved("browser-delayed-startup-finished")
+ .then(([win]) => win);
+}
+
+/**
+ * Opens the context menu.
+ * @param aWindow The window to open the context menu in.
+ * @param aLinkId The id of the link to open the context menu on.
+ * @return {Promise}
+ * @resolves With the menu popup when the context menu is open.
+ */
+function contextMenuOpened(aWindow, aLinkId) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(aWindow.document,
+ "popupshown");
+ // Simulate right-click.
+ clickTheLink(aWindow, aLinkId, { type: "contextmenu", button: 2 });
+ return popupShownPromise.then(e => e.target);
+}
+
+/**
+ * Performs a context menu command.
+ * @param aWindow The window with the already open context menu.
+ * @param aMenu The menu popup to hide.
+ * @param aItemId The id of the menu item to activate.
+ */
+function doContextMenuCommand(aWindow, aMenu, aItemId) {
+ let command = aWindow.document.getElementById(aItemId);
+ command.doCommand();
+ aMenu.hidePopup();
+}
+
+/**
+ * Loads a single test case, i.e., a source url into gTestWindow.
+ * @param aTestNumber The test case number - 0, 1, 2...
+ * @return {Promise}
+ * @resolves When the source url for this test case is loaded.
+ */
+function referrerTestCaseLoaded(aTestNumber, aParams) {
+ let test = getReferrerTest(aTestNumber);
+ let server = rounds == 0 ? REFERRER_POLICYSERVER_URL :
+ REFERRER_POLICYSERVER_URL_ATTRIBUTE;
+ let url = test.fromScheme + server +
+ "?scheme=" + escape(test.toScheme) +
+ "&policy=" + escape(test.policy || "") +
+ "&rel=" + escape(test.rel || "");
+ let browser = gTestWindow.gBrowser;
+ return BrowserTestUtils.openNewForegroundTab(browser, () => {
+ browser.selectedTab = browser.addTab(url, aParams);
+ }, false, true);
+}
+
+/**
+ * Checks the result of the referrer test, and moves on to the next test.
+ * @param aTestNumber The test number - 0, 1, 2, ...
+ * @param aNewWindow The new window where the referrer target opened, or null.
+ * @param aNewTab The new tab where the referrer target opened, or null.
+ * @param aStartTestCase The callback to start the next test, aTestNumber + 1.
+ */
+function checkReferrerAndStartNextTest(aTestNumber, aNewWindow, aNewTab,
+ aStartTestCase, aParams = {}) {
+ referrerResultExtracted(aNewWindow || gTestWindow).then(function(result) {
+ // Compare the actual result against the expected one.
+ let test = getReferrerTest(aTestNumber);
+ let desc = getReferrerTestDescription(aTestNumber);
+ is(result, test.result, desc);
+
+ // Clean up - close new tab / window, and then the source tab.
+ aNewTab && (aNewWindow || gTestWindow).gBrowser.removeTab(aNewTab);
+ aNewWindow && aNewWindow.close();
+ is(gTestWindow.gBrowser.tabs.length, 2, "two tabs open");
+ gTestWindow.gBrowser.removeTab(gTestWindow.gBrowser.tabs[1]);
+
+ // Move on to the next test. Or finish if we're done.
+ var nextTestNumber = aTestNumber + 1;
+ if (getReferrerTest(nextTestNumber)) {
+ referrerTestCaseLoaded(nextTestNumber, aParams).then(function() {
+ aStartTestCase(nextTestNumber);
+ });
+ } else if (rounds == 0) {
+ nextTestNumber = 0;
+ rounds = 1;
+ referrerTestCaseLoaded(nextTestNumber, aParams).then(function() {
+ aStartTestCase(nextTestNumber);
+ });
+ } else {
+ finish();
+ }
+ });
+}
+
+/**
+ * Fires up the complete referrer test.
+ * @param aStartTestCase The callback to start a single test case, called with
+ * the test number - 0, 1, 2... Needs to trigger the navigation from the source
+ * page, and call checkReferrerAndStartNextTest() when the target is loaded.
+ */
+function startReferrerTest(aStartTestCase, params = {}) {
+ waitForExplicitFinish();
+
+ // Open the window where we'll load the source URLs.
+ gTestWindow = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+ registerCleanupFunction(function() {
+ gTestWindow && gTestWindow.close();
+ });
+
+ // Load and start the first test.
+ delayedStartupFinished(gTestWindow).then(function() {
+ referrerTestCaseLoaded(0, params).then(function() {
+ aStartTestCase(0);
+ });
+ });
+}
diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini
new file mode 100644
index 000000000..6ad3668fd
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_identityBlock_focus.js]
+skip-if = os == 'mac' # Bug 1334418 (try only)
+support-files = ../general/permissions.html
+[browser_identityPopup_focus.js]
diff --git a/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js
new file mode 100644
index 000000000..e1e4e537a
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js
@@ -0,0 +1,62 @@
+/* Tests that the identity block can be reached via keyboard
+ * shortcuts and that it has the correct tab order.
+ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+const PERMISSIONS_PAGE = TEST_PATH + "permissions.html";
+
+function synthesizeKeyAndWaitForFocus(element, keyCode, options) {
+ let focused = BrowserTestUtils.waitForEvent(element, "focus");
+ EventUtils.synthesizeKey(keyCode, options);
+ return focused;
+}
+
+// Checks that the identity block is the next element after the urlbar
+// to be focused if there are no active notification anchors.
+add_task(function* testWithoutNotifications() {
+ yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+ yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true})
+ is(document.activeElement, gURLBar.inputField, "urlbar should be focused");
+ yield synthesizeKeyAndWaitForFocus(gIdentityHandler._identityBox, "VK_TAB", {shiftKey: true})
+ is(document.activeElement, gIdentityHandler._identityBox,
+ "identity block should be focused");
+ });
+});
+
+// Checks that when there is a notification anchor, it will receive
+// focus before the identity block.
+add_task(function* testWithoutNotifications() {
+
+ yield BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, function*(browser) {
+ let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ // Request a permission;
+ BrowserTestUtils.synthesizeMouseAtCenter("#geo", {}, browser);
+ yield popupshown;
+
+ yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true})
+ is(document.activeElement, gURLBar.inputField, "urlbar should be focused");
+ let geoIcon = document.getElementById("geo-notification-icon");
+ yield synthesizeKeyAndWaitForFocus(geoIcon, "VK_TAB", {shiftKey: true})
+ is(document.activeElement, geoIcon, "notification anchor should be focused");
+ yield synthesizeKeyAndWaitForFocus(gIdentityHandler._identityBox, "VK_TAB", {shiftKey: true})
+ is(document.activeElement, gIdentityHandler._identityBox,
+ "identity block should be focused");
+ });
+});
+
+// Checks that with invalid pageproxystate the identity block is ignored.
+add_task(function* testInvalidPageProxyState() {
+ yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
+ // Loading about:blank will automatically focus the urlbar, which, however, can
+ // race with the test code. So we only send the shortcut if the urlbar isn't focused yet.
+ if (document.activeElement != gURLBar.inputField) {
+ yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true})
+ }
+ is(document.activeElement, gURLBar.inputField, "urlbar should be focused");
+ yield synthesizeKeyAndWaitForFocus(gBrowser.getTabForBrowser(browser), "VK_TAB", {shiftKey: true})
+ isnot(document.activeElement, gIdentityHandler._identityBox,
+ "identity block should not be focused");
+ // Restore focus to the url bar.
+ gURLBar.focus();
+ });
+});
diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
new file mode 100644
index 000000000..eea06f079
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
@@ -0,0 +1,27 @@
+/* Tests the focus behavior of the identity popup. */
+
+// Access the identity popup via mouseclick. Focus should not be moved inside.
+add_task(function* testIdentityPopupFocusClick() {
+ yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+ let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gIdentityHandler._identityBox, {});
+ yield shown;
+ isnot(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
+ });
+});
+
+// Access the identity popup via keyboard. Focus should be moved inside.
+add_task(function* testIdentityPopupFocusKeyboard() {
+ yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+ let focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityBox, "focus");
+ gIdentityHandler._identityBox.focus();
+ yield focused;
+ let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ EventUtils.synthesizeKey(" ", {});
+ yield shown;
+ is(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
+ });
+});
+
diff --git a/browser/base/content/test/siteIdentity/head.js b/browser/base/content/test/siteIdentity/head.js
new file mode 100644
index 000000000..12a0547ee
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/head.js
@@ -0,0 +1,6 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
diff --git a/browser/base/content/test/social/.eslintrc.js b/browser/base/content/test/social/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/social/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/social/blocklist.xml b/browser/base/content/test/social/blocklist.xml
new file mode 100644
index 000000000..2e3665c36
--- /dev/null
+++ b/browser/base/content/test/social/blocklist.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+ <emItems>
+ <emItem blockID="s1" id="test1.example.com@services.mozilla.org"></emItem>
+ </emItems>
+</blocklist>
diff --git a/browser/base/content/test/social/browser.ini b/browser/base/content/test/social/browser.ini
new file mode 100644
index 000000000..91f931602
--- /dev/null
+++ b/browser/base/content/test/social/browser.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ blocklist.xml
+ head.js
+ opengraph/og_invalid_url.html
+ opengraph/opengraph.html
+ opengraph/shortlink_linkrel.html
+ opengraph/shorturl_link.html
+ opengraph/shorturl_linkrel.html
+ microformats.html
+ share.html
+ share_activate.html
+ social_activate.html
+ social_activate_basic.html
+ social_activate_iframe.html
+ social_postActivation.html
+ !/browser/base/content/test/plugins/blockNoPlugins.xml
+
+[browser_aboutHome_activation.js]
+[browser_addons.js]
+[browser_blocklist.js]
+[browser_share.js]
+[browser_social_activation.js]
diff --git a/browser/base/content/test/social/browser_aboutHome_activation.js b/browser/base/content/test/social/browser_aboutHome_activation.js
new file mode 100644
index 000000000..37cca53d2
--- /dev/null
+++ b/browser/base/content/test/social/browser_aboutHome_activation.js
@@ -0,0 +1,229 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHome.jsm");
+
+var snippet =
+' <script>' +
+' var manifest = {' +
+' "name": "Demo Social Service",' +
+' "origin": "https://example.com",' +
+' "iconURL": "chrome://branding/content/icon16.png",' +
+' "icon32URL": "chrome://branding/content/icon32.png",' +
+' "icon64URL": "chrome://branding/content/icon64.png",' +
+' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' +
+' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' +
+' };' +
+' function activateProvider(node) {' +
+' node.setAttribute("data-service", JSON.stringify(manifest));' +
+' var event = new CustomEvent("ActivateSocialFeature");' +
+' node.dispatchEvent(event);' +
+' }' +
+' </script>' +
+' <div id="activationSnippet" onclick="activateProvider(this)">' +
+' <img src="chrome://branding/content/icon32.png"></img>' +
+' </div>';
+
+// enable one-click activation
+var snippet2 =
+' <script>' +
+' var manifest = {' +
+' "name": "Demo Social Service",' +
+' "origin": "https://example.com",' +
+' "iconURL": "chrome://branding/content/icon16.png",' +
+' "icon32URL": "chrome://branding/content/icon32.png",' +
+' "icon64URL": "chrome://branding/content/icon64.png",' +
+' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' +
+' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' +
+' "oneclick": true' +
+' };' +
+' function activateProvider(node) {' +
+' node.setAttribute("data-service", JSON.stringify(manifest));' +
+' var event = new CustomEvent("ActivateSocialFeature");' +
+' node.dispatchEvent(event);' +
+' }' +
+' </script>' +
+' <div id="activationSnippet" onclick="activateProvider(this)">' +
+' <img src="chrome://branding/content/icon32.png"></img>' +
+' </div>';
+
+var gTests = [
+
+{
+ desc: "Test activation with enable panel",
+ snippet: snippet,
+ panel: true
+},
+
+{
+ desc: "Test activation bypassing enable panel",
+ snippet: snippet2,
+ panel: false
+}
+];
+
+function test()
+{
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+ ignoreAllUncaughtExceptions();
+ PopupNotifications.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ PopupNotifications.panel.removeAttribute("animate");
+ });
+
+ Task.spawn(function* () {
+ for (let test of gTests) {
+ info(test.desc);
+
+ // Create a tab to run the test.
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // Add an event handler to modify the snippets map once it's ready.
+ let snippetsPromise = promiseSetupSnippetsMap(tab, test.snippet);
+
+ // Start loading about:home and wait for it to complete, snippets should be loaded
+ yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsCompleted");
+
+ yield snippetsPromise;
+
+ // ensure our activation snippet is indeed available
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function*(arg) {
+ ok(!!content.document.getElementById("snippets"), "Found snippets element");
+ ok(!!content.document.getElementById("activationSnippet"), "The snippet is present.");
+ });
+
+ yield new Promise(resolve => {
+ activateProvider(tab, test.panel).then(() => {
+ checkSocialUI();
+ SocialService.uninstallProvider("https://example.com", function () {
+ info("provider uninstalled");
+ resolve();
+ });
+ });
+ });
+
+ // activation opened a post-activation info tab, close it.
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
+
+/**
+ * Starts a load in an existing tab and waits for it to finish (via some event).
+ *
+ * @param aTab
+ * The tab to load into.
+ * @param aUrl
+ * The url to load.
+ * @param aEvent
+ * The load event type to wait for. Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ */
+function promiseTabLoadEvent(aTab, aURL, aEventType="load")
+{
+ return new Promise(resolve => {
+ info("Wait tab event: " + aEventType);
+ aTab.linkedBrowser.addEventListener(aEventType, function load(event) {
+ if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ aTab.linkedBrowser.removeEventListener(aEventType, load, true);
+ info("Tab event received: " + aEventType);
+ resolve();
+ }, true, true);
+ aTab.linkedBrowser.loadURI(aURL);
+ });
+}
+
+/**
+ * Cleans up snippets and ensures that by default we don't try to check for
+ * remote snippets since that may cause network bustage or slowness.
+ *
+ * @param aTab
+ * The tab containing about:home.
+ * @param aSetupFn
+ * The setup function to be run.
+ * @return {Promise} resolved when the snippets are ready. Gets the snippets map.
+ */
+function promiseSetupSnippetsMap(aTab, aSnippet)
+{
+ info("Waiting for snippets map");
+
+ return ContentTask.spawn(aTab.linkedBrowser,
+ {snippetsVersion: AboutHomeUtils.snippetsVersion,
+ snippet: aSnippet},
+ function*(arg) {
+ return new Promise(resolve => {
+ addEventListener("AboutHomeLoadSnippets", function load(event) {
+ removeEventListener("AboutHomeLoadSnippets", load, true);
+
+ let cw = content.window.wrappedJSObject;
+
+ // The snippets should already be ready by this point. Here we're
+ // just obtaining a reference to the snippets map.
+ cw.ensureSnippetsMapThen(function (aSnippetsMap) {
+ aSnippetsMap = Cu.waiveXrays(aSnippetsMap);
+ console.log("Got snippets map: " +
+ "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
+ ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
+ " }");
+ // Don't try to update.
+ aSnippetsMap.set("snippets-last-update", Date.now());
+ aSnippetsMap.set("snippets-cached-version", arg.snippetsVersion);
+ // Clear snippets.
+ aSnippetsMap.delete("snippets");
+ aSnippetsMap.set("snippets", arg.snippet);
+ resolve();
+ });
+ }, true, true);
+ });
+ });
+}
+
+
+function sendActivationEvent(tab) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ let doc = tab.linkedBrowser.contentDocument;
+ // if our test has a frame, use it
+ if (doc.defaultView.frames[0])
+ doc = doc.defaultView.frames[0].document;
+ let button = doc.getElementById("activationSnippet");
+ BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser);
+}
+
+function activateProvider(tab, expectPanel, aCallback) {
+ return new Promise(resolve => {
+ if (expectPanel) {
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
+ let panel = document.getElementById("servicesInstall-notification");
+ panel.button.click();
+ });
+ }
+ waitForProviderLoad().then(() => {
+ checkSocialUI();
+ resolve();
+ });
+ sendActivationEvent(tab);
+ });
+}
+
+function waitForProviderLoad(cb) {
+ return Promise.all([
+ promiseObserverNotified("social:provider-enabled"),
+ ensureFrameLoaded(gBrowser, "https://example.com/browser/browser/base/content/test/social/social_postActivation.html"),
+ ]);
+}
diff --git a/browser/base/content/test/social/browser_addons.js b/browser/base/content/test/social/browser_addons.js
new file mode 100644
index 000000000..5a75d1d67
--- /dev/null
+++ b/browser/base/content/test/social/browser_addons.js
@@ -0,0 +1,217 @@
+var AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager;
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+var manifest = {
+ name: "provider 1",
+ origin: "https://example.com",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
+};
+var manifest2 = { // used for testing install
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
+ version: "1.0"
+};
+var manifestUpgrade = { // used for testing install
+ name: "provider 3",
+ origin: "https://test2.example.com",
+ shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
+ version: "1.0"
+};
+
+function test() {
+ waitForExplicitFinish();
+ PopupNotifications.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ PopupNotifications.panel.removeAttribute("animate");
+ });
+
+ let prefname = getManifestPrefname(manifest);
+ // ensure that manifest2 is NOT showing as builtin
+ is(SocialService.getOriginActivationType(manifest.origin), "foreign", "manifest is foreign");
+ is(SocialService.getOriginActivationType(manifest2.origin), "foreign", "manifest2 is foreign");
+
+ Services.prefs.setBoolPref("social.remote-install.enabled", true);
+ runSocialTests(tests, undefined, undefined, function () {
+ Services.prefs.clearUserPref("social.remote-install.enabled");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ // just in case the tests failed, clear these here as well
+ Services.prefs.clearUserPref("social.directories");
+ finish();
+ });
+}
+
+function installListener(next, aManifest) {
+ let expectEvent = "onInstalling";
+ let prefname = getManifestPrefname(aManifest);
+ // wait for the actual removal to call next
+ SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+ if (topic == "provider-disabled") {
+ SocialService.unregisterProviderListener(providerListener);
+ is(origin, aManifest.origin, "provider disabled");
+ executeSoon(next);
+ }
+ });
+
+ return {
+ onInstalling: function(addon) {
+ is(expectEvent, "onInstalling", "install started");
+ is(addon.manifest.origin, aManifest.origin, "provider about to be installed");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ expectEvent = "onInstalled";
+ },
+ onInstalled: function(addon) {
+ is(addon.manifest.origin, aManifest.origin, "provider installed");
+ ok(addon.installDate.getTime() > 0, "addon has installDate");
+ ok(addon.updateDate.getTime() > 0, "addon has updateDate");
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ expectEvent = "onUninstalling";
+ },
+ onUninstalling: function(addon) {
+ is(expectEvent, "onUninstalling", "uninstall started");
+ is(addon.manifest.origin, aManifest.origin, "provider about to be uninstalled");
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ expectEvent = "onUninstalled";
+ },
+ onUninstalled: function(addon) {
+ is(expectEvent, "onUninstalled", "provider has been uninstalled");
+ is(addon.manifest.origin, aManifest.origin, "provider uninstalled");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ AddonManager.removeAddonListener(this);
+ }
+ };
+}
+
+var tests = {
+ testHTTPInstallFailure: function(next) {
+ let installFrom = "http://example.com";
+ is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install");
+ let data = {
+ origin: installFrom,
+ url: installFrom+"/activate",
+ manifest: manifest,
+ window: window
+ }
+ Social.installProvider(data, function(addonManifest) {
+ ok(!addonManifest, "unable to install provider over http");
+ next();
+ });
+ },
+ testAddonEnableToggle: function(next) {
+ let expectEvent;
+ let prefname = getManifestPrefname(manifest);
+ let listener = {
+ onEnabled: function(addon) {
+ is(expectEvent, "onEnabled", "provider onEnabled");
+ ok(!addon.userDisabled, "provider enabled");
+ executeSoon(function() {
+ expectEvent = "onDisabling";
+ addon.userDisabled = true;
+ });
+ },
+ onEnabling: function(addon) {
+ is(expectEvent, "onEnabling", "provider onEnabling");
+ expectEvent = "onEnabled";
+ },
+ onDisabled: function(addon) {
+ is(expectEvent, "onDisabled", "provider onDisabled");
+ ok(addon.userDisabled, "provider disabled");
+ AddonManager.removeAddonListener(listener);
+ // clear the provider user-level pref
+ Services.prefs.clearUserPref(prefname);
+ executeSoon(next);
+ },
+ onDisabling: function(addon) {
+ is(expectEvent, "onDisabling", "provider onDisabling");
+ expectEvent = "onDisabled";
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ // we're only testing enable disable, so we quickly set the user-level pref
+ // for this provider and test enable/disable toggling
+ setManifestPref(prefname, manifest);
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ AddonManager.getAddonsByTypes(["service"], function(addons) {
+ for (let addon of addons) {
+ if (addon.userDisabled) {
+ expectEvent = "onEnabling";
+ addon.userDisabled = false;
+ // only test with one addon
+ return;
+ }
+ }
+ ok(false, "no addons toggled");
+ next();
+ });
+ },
+ testProviderEnableToggle: function(next) {
+ // enable and disabel a provider from the SocialService interface, check
+ // that the addon manager is updated
+
+ let expectEvent;
+ let prefname = getManifestPrefname(manifest);
+
+ let listener = {
+ onEnabled: function(addon) {
+ is(expectEvent, "onEnabled", "provider onEnabled");
+ is(addon.manifest.origin, manifest.origin, "provider enabled");
+ ok(!addon.userDisabled, "provider !userDisabled");
+ },
+ onEnabling: function(addon) {
+ is(expectEvent, "onEnabling", "provider onEnabling");
+ is(addon.manifest.origin, manifest.origin, "provider about to be enabled");
+ expectEvent = "onEnabled";
+ },
+ onDisabled: function(addon) {
+ is(expectEvent, "onDisabled", "provider onDisabled");
+ is(addon.manifest.origin, manifest.origin, "provider disabled");
+ ok(addon.userDisabled, "provider userDisabled");
+ },
+ onDisabling: function(addon) {
+ is(expectEvent, "onDisabling", "provider onDisabling");
+ is(addon.manifest.origin, manifest.origin, "provider about to be disabled");
+ expectEvent = "onDisabled";
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ expectEvent = "onEnabling";
+ setManifestPref(prefname, manifest);
+ SocialService.enableProvider(manifest.origin, function(provider) {
+ expectEvent = "onDisabling";
+ SocialService.disableProvider(provider.origin, function() {
+ AddonManager.removeAddonListener(listener);
+ Services.prefs.clearUserPref(prefname);
+ next();
+ });
+ });
+ },
+ testDirectoryInstall: function(next) {
+ AddonManager.addAddonListener(installListener(next, manifest2));
+
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
+ let panel = document.getElementById("servicesInstall-notification");
+ info("servicesInstall-notification panel opened");
+ panel.button.click();
+ });
+
+ Services.prefs.setCharPref("social.directories", manifest2.origin);
+ is(SocialService.getOriginActivationType(manifest2.origin), "directory", "testing directory install");
+ let data = {
+ origin: manifest2.origin,
+ url: manifest2.origin + "/directory",
+ manifest: manifest2,
+ window: window
+ }
+ Social.installProvider(data, function(addonManifest) {
+ Services.prefs.clearUserPref("social.directories");
+ SocialService.enableProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ });
+ });
+ }
+}
diff --git a/browser/base/content/test/social/browser_blocklist.js b/browser/base/content/test/social/browser_blocklist.js
new file mode 100644
index 000000000..b67d5efb3
--- /dev/null
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -0,0 +1,211 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// a place for miscellaneous social tests
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+var blocklistURL = "http://example.com/browser/browser/base/content/test/social/blocklist.xml";
+
+var manifest = { // normal provider
+ name: "provider ok",
+ origin: "https://example.com",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
+};
+var manifest_bad = { // normal provider
+ name: "provider blocked",
+ origin: "https://test1.example.com",
+ shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png"
+};
+
+// blocklist testing
+function updateBlocklist() {
+ var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ let promise = promiseObserverNotified("blocklist-updated");
+ blocklistNotifier.notify(null);
+ return promise;
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL) {
+ if (!_originalTestBlocklistURL)
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ return updateBlocklist();
+}
+
+function resetBlocklist() {
+ // XXX - this has "forked" from the head.js helpers in our parent directory :(
+ // But let's reuse their blockNoPlugins.xml. Later, we should arrange to
+ // use their head.js helpers directly
+ let noBlockedURL = "http://example.com/browser/browser/base/content/test/plugins/blockNoPlugins.xml";
+ return new Promise(resolve => {
+ setAndUpdateBlocklist(noBlockedURL).then(() => {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+ resolve();
+ });
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ // turn on logging for nsBlocklistService.js
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("extensions.logging.enabled");
+ });
+
+ runSocialTests(tests, undefined, undefined, function () {
+ resetBlocklist().then(finish); // restore to original pref
+ });
+}
+
+var tests = {
+ testSimpleBlocklist: function(next) {
+ // this really just tests adding and clearing our blocklist for later tests
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ ok(Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocking 'blocked'");
+ ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest)), "not blocking 'good'");
+ resetBlocklist().then(() => {
+ ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocklist cleared");
+ next();
+ });
+ });
+ },
+ testAddingNonBlockedProvider: function(next) {
+ function finishTest(isgood) {
+ ok(isgood, "adding non-blocked provider ok");
+ Services.prefs.clearUserPref("social.manifest.good");
+ resetBlocklist().then(next);
+ }
+ setManifestPref("social.manifest.good", manifest);
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ try {
+ SocialService.addProvider(manifest, function(provider) {
+ try {
+ SocialService.disableProvider(provider.origin, function() {
+ ok(true, "added and removed provider");
+ finishTest(true);
+ });
+ } catch (e) {
+ ok(false, "SocialService.disableProvider threw exception: " + e);
+ finishTest(false);
+ }
+ });
+ } catch (e) {
+ ok(false, "SocialService.addProvider threw exception: " + e);
+ finishTest(false);
+ }
+ });
+ },
+ testAddingBlockedProvider: function(next) {
+ function finishTest(good) {
+ ok(good, "Unable to add blocklisted provider");
+ Services.prefs.clearUserPref("social.manifest.blocked");
+ resetBlocklist().then(next);
+ }
+ setManifestPref("social.manifest.blocked", manifest_bad);
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ try {
+ SocialService.addProvider(manifest_bad, function(provider) {
+ SocialService.disableProvider(provider.origin, function() {
+ ok(false, "SocialService.addProvider should throw blocklist exception");
+ finishTest(false);
+ });
+ });
+ } catch (e) {
+ ok(true, "SocialService.addProvider should throw blocklist exception: " + e);
+ finishTest(true);
+ }
+ });
+ },
+ testInstallingBlockedProvider: function(next) {
+ function finishTest(good) {
+ ok(good, "Unable to install blocklisted provider");
+ resetBlocklist().then(next);
+ }
+ let activationURL = manifest_bad.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ try {
+ // expecting an exception when attempting to install a hard blocked
+ // provider
+ let data = {
+ origin: manifest_bad.origin,
+ url: activationURL,
+ manifest: manifest_bad,
+ window: window
+ }
+ Social.installProvider(data, function(addonManifest) {
+ finishTest(false);
+ });
+ } catch (e) {
+ finishTest(true);
+ }
+ });
+ },
+ testBlockingExistingProvider: function(next) {
+ let listener = {
+ _window: null,
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+ this._window = aXULWindow;
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ domwindow.addEventListener("load", function _load() {
+ domwindow.removeEventListener("load", _load, false);
+
+ domwindow.addEventListener("unload", function _unload() {
+ domwindow.removeEventListener("unload", _unload, false);
+ info("blocklist window was closed");
+ Services.wm.removeListener(listener);
+ next();
+ }, false);
+
+ is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused");
+ // wait until after load to cancel so the dialog has initalized. we
+ // don't want to accept here since that restarts the browser.
+ executeSoon(() => {
+ let cancelButton = domwindow.document.documentElement.getButton("cancel");
+ info("***** hit the cancel button\n");
+ cancelButton.doCommand();
+ });
+ }, false);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ };
+
+ Services.wm.addListener(listener);
+
+ setManifestPref("social.manifest.blocked", manifest_bad);
+ try {
+ SocialService.addProvider(manifest_bad, function(provider) {
+ // the act of blocking should cause a 'provider-disabled' notification
+ // from SocialService.
+ SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+ if (topic != "provider-disabled")
+ return;
+ SocialService.unregisterProviderListener(providerListener);
+ is(origin, provider.origin, "provider disabled");
+ SocialService.getProvider(provider.origin, function(p) {
+ ok(p == null, "blocklisted provider disabled");
+ Services.prefs.clearUserPref("social.manifest.blocked");
+ resetBlocklist();
+ });
+ });
+ // no callback - the act of updating should cause the listener above
+ // to fire.
+ setAndUpdateBlocklist(blocklistURL);
+ });
+ } catch (e) {
+ ok(false, "unable to add provider " + e);
+ next();
+ }
+ }
+}
diff --git a/browser/base/content/test/social/browser_share.js b/browser/base/content/test/social/browser_share.js
new file mode 100644
index 000000000..19dca519b
--- /dev/null
+++ b/browser/base/content/test/social/browser_share.js
@@ -0,0 +1,396 @@
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+var baseURL = "https://example.com/browser/browser/base/content/test/social/";
+
+var manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
+};
+var activationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html";
+
+function sendActivationEvent(subframe) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ let doc = subframe.contentDocument;
+ // if our test has a frame, use it
+ let button = doc.getElementById("activation");
+ ok(!!button, "got the activation button");
+ EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView);
+}
+
+function test() {
+ waitForExplicitFinish();
+ Services.prefs.setCharPref("social.shareDirectory", activationPage);
+
+ let frameScript = "data:,(" + function frame_script() {
+ addEventListener("OpenGraphData", function (aEvent) {
+ sendAsyncMessage("sharedata", aEvent.detail);
+ }, true, true);
+ /* bug 1042991, ensure history is available by calling history.back on close */
+ addMessageListener("closeself", function(e) {
+ content.history.back();
+ content.close();
+ }, true);
+ /* if text is entered into field, onbeforeunload will cause a modal dialog
+ unless dialogs have been disabled for the iframe. */
+ content.onbeforeunload = function(e) {
+ return 'FAIL.';
+ };
+ }.toString() + ")();";
+ let mm = getGroupMessageManager("social");
+ mm.loadFrameScript(frameScript, true);
+
+ // Animation on the panel can cause intermittent failures such as bug 1115131.
+ SocialShare.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ SocialShare.panel.removeAttribute("animate");
+ mm.removeDelayedFrameScript(frameScript);
+ Services.prefs.clearUserPref("social.directories");
+ Services.prefs.clearUserPref("social.shareDirectory");
+ Services.prefs.clearUserPref("social.share.activationPanelEnabled");
+ });
+ runSocialTests(tests, undefined, function(next) {
+ let shareButton = SocialShare.shareButton;
+ if (shareButton) {
+ CustomizableUI.removeWidgetFromArea("social-share-button", CustomizableUI.AREA_NAVBAR)
+ shareButton.remove();
+ }
+ next();
+ });
+}
+
+var corpus = [
+ {
+ url: baseURL+"opengraph/opengraph.html",
+ options: {
+ // og:title
+ title: ">This is my title<",
+ // og:description
+ description: "A test corpus file for open graph tags we care about",
+ // medium: this.getPageMedium(),
+ // source: this.getSourceURL(),
+ // og:url
+ url: "https://www.mozilla.org/",
+ // shortUrl: this.getShortURL(),
+ // og:image
+ previews:["https://www.mozilla.org/favicon.png"],
+ // og:site_name
+ siteName: ">My simple test page<"
+ }
+ },
+ {
+ // tests that og:url doesn't override the page url if it is bad
+ url: baseURL+"opengraph/og_invalid_url.html",
+ options: {
+ description: "A test corpus file for open graph tags passing a bad url",
+ url: baseURL+"opengraph/og_invalid_url.html",
+ previews: [],
+ siteName: "Evil chrome delivering website"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shorturl_link.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shorturl_linkrel.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shortlink_linkrel.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ }
+];
+
+function hasoptions(testOptions, options) {
+ for (let option in testOptions) {
+ let data = testOptions[option];
+ info("data: "+JSON.stringify(data));
+ let message_data = options[option];
+ info("message_data: "+JSON.stringify(message_data));
+ if (Array.isArray(data)) {
+ // the message may have more array elements than we are testing for, this
+ // is ok since some of those are hard to test. So we just test that
+ // anything in our test data IS in the message.
+ ok(Array.every(data, function(item) { return message_data.indexOf(item) >= 0 }), "option "+option);
+ } else {
+ is(message_data, data, "option "+option);
+ }
+ }
+}
+
+var tests = {
+ testShareDisabledOnActivation: function(next) {
+ // starting on about:blank page, share should be visible but disabled when
+ // adding provider
+ is(gBrowser.currentURI.spec, "about:blank");
+
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ SocialService.addProvider(manifest, function(provider) {
+ is(SocialUI.enabled, true, "SocialUI is enabled");
+ checkSocialUI();
+ // share should not be enabled since we only have about:blank page
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+ SocialService.disableProvider(manifest.origin, next);
+ });
+ },
+ testShareEnabledOnActivation: function(next) {
+ // starting from *some* page, share should be visible and enabled when
+ // activating provider
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ let testData = corpus[0];
+ BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => {
+ SocialService.addProvider(manifest, function(provider) {
+ is(SocialUI.enabled, true, "SocialUI is enabled");
+ checkSocialUI();
+ // share should not be enabled since we only have about:blank page
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+ BrowserTestUtils.removeTab(tab).then(next);
+ });
+ });
+ },
+ testSharePage: function(next) {
+ let testTab;
+ let testIndex = 0;
+ let testData = corpus[testIndex++];
+
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ let mm = getGroupMessageManager("social");
+ mm.addMessageListener("sharedata", function handler(msg) {
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ hasoptions(testData.options, JSON.parse(msg.data));
+ testData = corpus[testIndex++];
+ BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, "share panel closed").then(() => {
+ if (testData) {
+ runOneTest();
+ } else {
+ mm.removeMessageListener("sharedata", handler);
+ SocialService.disableProvider(manifest.origin, next);
+ }
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+ });
+
+ function runOneTest() {
+ BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => {
+ testTab = tab;
+
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ SocialShare.sharePage(manifest.origin);
+ });
+ }
+ executeSoon(runOneTest);
+ },
+ testShareMicroformats: function(next) {
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ SocialService.addProvider(manifest, function(provider) {
+ let target, testTab;
+
+ let expecting = JSON.stringify({
+ "url": "https://example.com/browser/browser/base/content/test/social/microformats.html",
+ "title": "Raspberry Pi Page",
+ "previews": ["https://example.com/someimage.jpg"],
+ "microformats": {
+ "items": [{
+ "type": ["h-product"],
+ "properties": {
+ "name": ["Raspberry Pi"],
+ "photo": ["https://example.com/someimage.jpg"],
+ "description": [{
+ "value": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.",
+ "html": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."
+ }
+ ],
+ "url": ["https://example.com/"],
+ "price": ["29.95"],
+ "review": [{
+ "value": "4.5 out of 5",
+ "type": ["h-review"],
+ "properties": {
+ "rating": ["4.5"]
+ }
+ }
+ ],
+ "category": ["Computer", "Education"]
+ }
+ }
+ ],
+ "rels": {
+ "tag": ["https://example.com/wiki/computer", "https://example.com/wiki/education"]
+ },
+ "rel-urls": {
+ "https://example.com/wiki/computer": {
+ "text": "Computer",
+ "rels": ["tag"]
+ },
+ "https://example.com/wiki/education": {
+ "text": "Education",
+ "rels": ["tag"]
+ }
+ }
+ }
+ });
+
+ let mm = getGroupMessageManager("social");
+ mm.addMessageListener("sharedata", function handler(msg) {
+ is(msg.data, expecting, "microformats data ok");
+ BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; },
+ "share panel closed").then(() => {
+ mm.removeMessageListener("sharedata", handler);
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ SocialService.disableProvider(manifest.origin, next);
+ });
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+
+ let url = "https://example.com/browser/browser/base/content/test/social/microformats.html"
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => {
+ testTab = tab;
+
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ let doc = tab.linkedBrowser.contentDocument;
+ target = doc.getElementById("simple-hcard");
+ SocialShare.sharePage(manifest.origin, null, target);
+ });
+ });
+ },
+ testSharePanelActivation: function(next) {
+ let testTab;
+ // cleared in the cleanup function
+ Services.prefs.setCharPref("social.directories", "https://example.com");
+ Services.prefs.setBoolPref("social.share.activationPanelEnabled", true);
+ // make the iframe so we can wait on the load
+ SocialShare._createFrame();
+ let iframe = SocialShare.iframe;
+
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ ensureFrameLoaded(iframe).then(() => {
+ let subframe = iframe.contentDocument.getElementById("activation-frame");
+ ensureFrameLoaded(subframe, activationPage).then(() => {
+ is(subframe.contentDocument.location.href, activationPage, "activation page loaded");
+ promiseObserverNotified("social:provider-enabled").then(() => {
+ let mm = getGroupMessageManager("social");
+ mm.addMessageListener("sharedata", function handler(msg) {
+ ok(true, "share completed");
+
+ BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; },
+ "share panel closed").then(() => {
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ mm.removeMessageListener("sharedata", handler);
+ SocialService.uninstallProvider(manifest.origin, next);
+ });
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+ });
+ sendActivationEvent(subframe);
+ });
+ });
+ BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => {
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ testTab = tab;
+ SocialShare.sharePage();
+ });
+ },
+ testSharePanelDialog: function(next) {
+ let testTab;
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+ SocialShare._createFrame();
+
+ SocialService.addProvider(manifest, () => {
+ BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => {
+ ensureFrameLoaded(SocialShare.iframe).then(() => {
+ // send keys to the input field. An unexpected failure will happen
+ // if the onbeforeunload handler is fired.
+ EventUtils.sendKey("f");
+ EventUtils.sendKey("a");
+ EventUtils.sendKey("i");
+ EventUtils.sendKey("l");
+
+ SocialShare.panel.addEventListener("popuphidden", function hidden(evt) {
+ SocialShare.panel.removeEventListener("popuphidden", hidden);
+ let topwin = Services.wm.getMostRecentWindow(null);
+ is(topwin, window, "no dialog is open");
+
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ SocialService.disableProvider(manifest.origin, next);
+ });
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ testTab = tab;
+ SocialShare.sharePage();
+ });
+ });
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_activation.js b/browser/base/content/test/social/browser_social_activation.js
new file mode 100644
index 000000000..2af0d8021
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_activation.js
@@ -0,0 +1,270 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: Assert is null");
+
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+var tabsToRemove = [];
+
+function removeProvider(provider) {
+ return new Promise(resolve => {
+ // a full install sets the manifest into a pref, addProvider alone doesn't,
+ // make sure we uninstall if the manifest was added.
+ if (provider.manifest) {
+ SocialService.uninstallProvider(provider.origin, resolve);
+ } else {
+ SocialService.disableProvider(provider.origin, resolve);
+ }
+ });
+}
+
+function postTestCleanup(callback) {
+ Task.spawn(function* () {
+ // any tabs opened by the test.
+ for (let tab of tabsToRemove) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ tabsToRemove = [];
+ // all the providers may have been added.
+ while (Social.providers.length > 0) {
+ yield removeProvider(Social.providers[0]);
+ }
+ }).then(callback);
+}
+
+function newTab(url) {
+ return new Promise(resolve => {
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => {
+ tabsToRemove.push(tab);
+ resolve(tab);
+ });
+ });
+}
+
+function sendActivationEvent(tab, callback, nullManifest) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ BrowserTestUtils.synthesizeMouseAtCenter("#activation", {}, tab.linkedBrowser);
+ executeSoon(callback);
+}
+
+function activateProvider(domain, callback, nullManifest) {
+ let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_basic.html"
+ newTab(activationURL).then(tab => {
+ sendActivationEvent(tab, callback, nullManifest);
+ });
+}
+
+function activateIFrameProvider(domain, callback) {
+ let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html"
+ newTab(activationURL).then(tab => {
+ sendActivationEvent(tab, callback, false);
+ });
+}
+
+function waitForProviderLoad(origin) {
+ return Promise.all([
+ ensureFrameLoaded(gBrowser, origin + "/browser/browser/base/content/test/social/social_postActivation.html"),
+ ]);
+}
+
+function getAddonItemInList(aId, aList) {
+ var item = aList.firstChild;
+ while (item) {
+ if ("mAddon" in item && item.mAddon.id == aId) {
+ aList.ensureElementIsVisible(item);
+ return item;
+ }
+ item = item.nextSibling;
+ }
+ return null;
+}
+
+function clickAddonRemoveButton(tab, aCallback) {
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ let addon = aAddons[0];
+
+ let doc = tab.linkedBrowser.contentDocument;
+ let list = doc.getElementById("addon-list");
+
+ let item = getAddonItemInList(addon.id, list);
+ let button = item._removeBtn;
+ isnot(button, null, "Should have a remove button");
+ ok(!button.disabled, "Button should not be disabled");
+
+ // uninstall happens after about:addons tab is closed, so we wait on
+ // disabled
+ promiseObserverNotified("social:provider-disabled").then(() => {
+ is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
+ executeSoon(function() { aCallback(addon); });
+ });
+
+ BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser);
+ });
+}
+
+function activateOneProvider(manifest, finishActivation, aCallback) {
+ info("activating provider "+manifest.name);
+ let panel = document.getElementById("servicesInstall-notification");
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
+ ok(!panel.hidden, "servicesInstall-notification panel opened");
+ if (finishActivation)
+ panel.button.click();
+ else
+ panel.closebutton.click();
+ });
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden").then(() => {
+ ok(panel.hidden, "servicesInstall-notification panel hidden");
+ if (!finishActivation) {
+ ok(panel.hidden, "activation panel is not showing");
+ executeSoon(aCallback);
+ } else {
+ waitForProviderLoad(manifest.origin).then(() => {
+ checkSocialUI();
+ executeSoon(aCallback);
+ });
+ }
+ });
+
+ // the test will continue as the popup events fire...
+ activateProvider(manifest.origin, function() {
+ info("waiting on activation panel to open/close...");
+ });
+}
+
+var gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
+var gProviders = [
+ {
+ name: "provider 1",
+ origin: "https://example.com",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html?provider1",
+ iconURL: "chrome://branding/content/icon48.png"
+ },
+ {
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html?provider2",
+ iconURL: "chrome://branding/content/icon64.png"
+ },
+ {
+ name: "provider 3",
+ origin: "https://test2.example.com",
+ shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html?provider2",
+ iconURL: "chrome://branding/content/about-logo.png"
+ }
+];
+
+
+function test() {
+ PopupNotifications.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ PopupNotifications.panel.removeAttribute("animate");
+ });
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, () => {
+ runSocialTests(tests, undefined, postTestCleanup);
+ });
+}
+
+var tests = {
+ testActivationWrongOrigin: function(next) {
+ // At this stage none of our providers exist, so we expect failure.
+ Services.prefs.setBoolPref("social.remote-install.enabled", false);
+ activateProvider(gTestDomains[0], function() {
+ is(SocialUI.enabled, false, "SocialUI is not enabled");
+ let panel = document.getElementById("servicesInstall-notification");
+ ok(panel.hidden, "activation panel still hidden");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.remote-install.enabled");
+ next();
+ });
+ },
+
+ testIFrameActivation: function(next) {
+ activateIFrameProvider(gTestDomains[0], function() {
+ is(SocialUI.enabled, false, "SocialUI is not enabled");
+ let panel = document.getElementById("servicesInstall-notification");
+ ok(panel.hidden, "activation panel still hidden");
+ checkSocialUI();
+ next();
+ });
+ },
+
+ testActivationFirstProvider: function(next) {
+ // first up we add a manifest entry for a single provider.
+ activateOneProvider(gProviders[0], false, function() {
+ // we deactivated leaving no providers left, so Social is disabled.
+ checkSocialUI();
+ next();
+ });
+ },
+
+ testActivationMultipleProvider: function(next) {
+ // The trick with this test is to make sure that Social.providers[1] is
+ // the current provider when doing the undo - this makes sure that the
+ // Social code doesn't fallback to Social.providers[0], which it will
+ // do in some cases (but those cases do not include what this test does)
+ // first enable the 2 providers
+ SocialService.addProvider(gProviders[0], function() {
+ SocialService.addProvider(gProviders[1], function() {
+ checkSocialUI();
+ // activate the last provider.
+ activateOneProvider(gProviders[2], false, function() {
+ // we deactivated - the first provider should be enabled.
+ checkSocialUI();
+ next();
+ });
+ });
+ });
+ },
+
+ testAddonManagerDoubleInstall: function(next) {
+ // Create a new tab and load about:addons
+ let addonsTab = gBrowser.addTab();
+ gBrowser.selectedTab = addonsTab;
+ BrowserOpenAddonsMgr('addons://list/service');
+ gBrowser.selectedBrowser.addEventListener("load", function tabLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true);
+ is(addonsTab.linkedBrowser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
+
+ activateOneProvider(gProviders[0], true, function() {
+ info("first activation completed");
+ is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
+ is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_activate_basic.html", "activation page selected");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
+ tabsToRemove.pop();
+ // uninstall the provider
+ clickAddonRemoveButton(addonsTab, function(addon) {
+ checkSocialUI();
+ activateOneProvider(gProviders[0], true, function() {
+ info("second activation completed");
+ is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
+
+ // after closing the addons tab, verify provider is still installed
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ is(aAddons.length, 1, "there can be only one");
+
+ let doc = addonsTab.linkedBrowser.contentDocument;
+ let list = doc.getElementById("addon-list");
+ is(list.childNodes.length, 1, "only one addon is displayed");
+
+ BrowserTestUtils.removeTab(addonsTab).then(next);
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ }, true);
+ }
+}
diff --git a/browser/base/content/test/social/head.js b/browser/base/content/test/social/head.js
new file mode 100644
index 000000000..ea175c97a
--- /dev/null
+++ b/browser/base/content/test/social/head.js
@@ -0,0 +1,273 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+
+function promiseObserverNotified(aTopic) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
+ dump("notification promised "+aTopic);
+ Services.obs.removeObserver(onNotification, aTopic);
+ TestUtils.executeSoon(() => resolve({subject: aSubject, data: aData}));
+ }, aTopic, false);
+ });
+}
+
+// Check that a specified (string) URL hasn't been "remembered" (ie, is not
+// in history, will not appear in about:newtab or auto-complete, etc.)
+function promiseSocialUrlNotRemembered(url) {
+ return new Promise(resolve => {
+ let uri = Services.io.newURI(url, null, null);
+ PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) {
+ ok(!aIsVisited, "social URL " + url + " should not be in global history");
+ resolve();
+ });
+ });
+}
+
+var gURLsNotRemembered = [];
+
+
+function checkProviderPrefsEmpty(isError) {
+ let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+ let prefs = MANIFEST_PREFS.getChildList("", []);
+ let c = 0;
+ for (let pref of prefs) {
+ if (MANIFEST_PREFS.prefHasUserValue(pref)) {
+ info("provider [" + pref + "] manifest left installed from previous test");
+ c++;
+ }
+ }
+ is(c, 0, "all provider prefs uninstalled from previous test");
+ is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length);
+}
+
+function defaultFinishChecks() {
+ checkProviderPrefsEmpty(true);
+ finish();
+}
+
+function runSocialTestWithProvider(manifest, callback, finishcallback) {
+
+ let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+ let manifests = Array.isArray(manifest) ? manifest : [manifest];
+
+ // Check that none of the provider's content ends up in history.
+ function* finishCleanUp() {
+ for (let i = 0; i < manifests.length; i++) {
+ let m = manifests[i];
+ for (let what of ['iconURL', 'shareURL']) {
+ if (m[what]) {
+ yield promiseSocialUrlNotRemembered(m[what]);
+ }
+ }
+ }
+ for (let i = 0; i < gURLsNotRemembered.length; i++) {
+ yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
+ }
+ gURLsNotRemembered = [];
+ }
+
+ info("runSocialTestWithProvider: " + manifests.toSource());
+
+ let finishCount = 0;
+ function finishIfDone(callFinish) {
+ finishCount++;
+ if (finishCount == manifests.length)
+ Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks);
+ }
+ function removeAddedProviders(cleanup) {
+ manifests.forEach(function (m) {
+ // If we're "cleaning up", don't call finish when done.
+ let callback = cleanup ? function () {} : finishIfDone;
+ // Similarly, if we're cleaning up, catch exceptions from removeProvider
+ let removeProvider = SocialService.disableProvider.bind(SocialService);
+ if (cleanup) {
+ removeProvider = function (origin, cb) {
+ try {
+ SocialService.disableProvider(origin, cb);
+ } catch (ex) {
+ // Ignore "provider doesn't exist" errors.
+ if (ex.message.indexOf("SocialService.disableProvider: no provider with origin") == 0)
+ return;
+ info("Failed to clean up provider " + origin + ": " + ex);
+ }
+ }
+ }
+ removeProvider(m.origin, callback);
+ });
+ }
+ function finishSocialTest(cleanup) {
+ removeAddedProviders(cleanup);
+ }
+
+ let providersAdded = 0;
+
+ manifests.forEach(function (m) {
+ SocialService.addProvider(m, function(provider) {
+
+ providersAdded++;
+ info("runSocialTestWithProvider: provider added");
+
+ // we want to set the first specified provider as the UI's provider
+ if (provider.origin == manifests[0].origin) {
+ firstProvider = provider;
+ }
+
+ // If we've added all the providers we need, call the callback to start
+ // the tests (and give it a callback it can call to finish them)
+ if (providersAdded == manifests.length) {
+ registerCleanupFunction(function () {
+ finishSocialTest(true);
+ });
+ BrowserTestUtils.waitForCondition(() => provider.enabled,
+ "providers added and enabled").then(() => {
+ info("provider has been enabled");
+ callback(finishSocialTest);
+ });
+ }
+ });
+ });
+}
+
+function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
+ let testIter = (function*() {
+ for (let name in tests) {
+ if (tests.hasOwnProperty(name)) {
+ yield [name, tests[name]];
+ }
+ }
+ })();
+ let providersAtStart = Social.providers.length;
+ info("runSocialTests: start test run with " + providersAtStart + " providers");
+ window.focus();
+
+
+ if (cbPreTest === undefined) {
+ cbPreTest = function(cb) { cb() };
+ }
+ if (cbPostTest === undefined) {
+ cbPostTest = function(cb) { cb() };
+ }
+
+ function runNextTest() {
+ let result = testIter.next();
+ if (result.done) {
+ // out of items:
+ (cbFinish || defaultFinishChecks)();
+ is(providersAtStart, Social.providers.length,
+ "runSocialTests: finish test run with " + Social.providers.length + " providers");
+ return;
+ }
+ let [name, func] = result.value;
+ // We run on a timeout to help keep the debug messages sane.
+ executeSoon(function() {
+ function cleanupAndRunNextTest() {
+ info("sub-test " + name + " complete");
+ cbPostTest(runNextTest);
+ }
+ cbPreTest(function() {
+ info("pre-test: starting with " + Social.providers.length + " providers");
+ info("sub-test " + name + " starting");
+ try {
+ func.call(tests, cleanupAndRunNextTest);
+ } catch (ex) {
+ ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
+ cleanupAndRunNextTest();
+ }
+ })
+ });
+ }
+ runNextTest();
+}
+
+// A fairly large hammer which checks all aspects of the SocialUI for
+// internal consistency.
+function checkSocialUI(win) {
+ let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+ // if we have enabled providers, we should also have instances of those
+ // providers
+ if (SocialService.hasEnabledProviders) {
+ ok(Social.providers.length > 0, "providers are enabled");
+ } else {
+ is(Social.providers.length, 0, "providers are not enabled");
+ }
+}
+
+function setManifestPref(name, manifest) {
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.setComplexValue(name, Ci.nsISupportsString, string);
+}
+
+function getManifestPrefname(aManifest) {
+ // is same as the generated name in SocialServiceInternal.getManifestPrefname
+ let originUri = Services.io.newURI(aManifest.origin, null, null);
+ return "social.manifest." + originUri.hostPort.replace('.', '-');
+}
+
+function ensureFrameLoaded(frame, uri) {
+ return new Promise(resolve => {
+ if (frame.contentDocument && frame.contentDocument.readyState == "complete" &&
+ (!uri || frame.contentDocument.location.href == uri)) {
+ resolve();
+ } else {
+ frame.addEventListener("load", function handler() {
+ if (uri && frame.contentDocument.location.href != uri)
+ return;
+ frame.removeEventListener("load", handler, true);
+ resolve()
+ }, true);
+ }
+ });
+}
+
+// Support for going on and offline.
+// (via browser/base/content/test/browser_bookmark_titles.js)
+var origProxyType = Services.prefs.getIntPref('network.proxy.type');
+
+function toggleOfflineStatus(goOffline) {
+ // Bug 968887 fix. when going on/offline, wait for notification before continuing
+ return new Promise(resolve => {
+ if (!goOffline) {
+ Services.prefs.setIntPref('network.proxy.type', origProxyType);
+ }
+ if (goOffline != Services.io.offline) {
+ info("initial offline state " + Services.io.offline);
+ let expect = !Services.io.offline;
+ Services.obs.addObserver(function offlineChange(subject, topic, data) {
+ Services.obs.removeObserver(offlineChange, "network:offline-status-changed");
+ info("offline state changed to " + Services.io.offline);
+ is(expect, Services.io.offline, "network:offline-status-changed successful toggle");
+ resolve();
+ }, "network:offline-status-changed", false);
+ BrowserOffline.toggleOfflineStatus();
+ } else {
+ resolve();
+ }
+ if (goOffline) {
+ Services.prefs.setIntPref('network.proxy.type', 0);
+ // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+ Services.cache2.clear();
+ }
+ });
+}
+
+function goOffline() {
+ // Simulate a network outage with offline mode. (Localhost is still
+ // accessible in offline mode, so disable the test proxy as well.)
+ return toggleOfflineStatus(true);
+}
+
+function goOnline(callback) {
+ return toggleOfflineStatus(false);
+}
diff --git a/browser/base/content/test/social/microformats.html b/browser/base/content/test/social/microformats.html
new file mode 100644
index 000000000..1a0e4436b
--- /dev/null
+++ b/browser/base/content/test/social/microformats.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <head><title>Raspberry Pi Page</title></head>
+ <div class="hproduct">
+ <h2 class="fn">Raspberry Pi</h2>
+ <img class="photo" src="https://example.com/someimage.jpg" />
+ <p class="description">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>
+ <a class="url" href="https://example.com/">More info about the Raspberry Pi</a>
+ <p class="price">29.95</p>
+ <p class="review hreview"><span id="test-review" class="rating">4.5</span> out of 5</p>
+ <p>Categories:
+ <a rel="tag" href="https://example.com/wiki/computer" class="category">Computer</a>,
+ <a rel="tag" href="https://example.com/wiki/education" class="category">Education</a>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/moz.png b/browser/base/content/test/social/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/social/moz.png
Binary files differ
diff --git a/browser/base/content/test/social/opengraph/og_invalid_url.html b/browser/base/content/test/social/opengraph/og_invalid_url.html
new file mode 100644
index 000000000..ad1dae2be
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/og_invalid_url.html
@@ -0,0 +1,11 @@
+<html xmlns:og="http://ogp.me/ns#">
+<head>
+ <meta property="og:url" content="chrome://browser/content/aboutDialog.xul"/>
+ <meta property="og:site_name" content="Evil chrome delivering website"/>
+ <meta property="og:description"
+ content="A test corpus file for open graph tags passing a bad url"/>
+</head>
+<body>
+ Open Graph Test Page
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/opengraph.html b/browser/base/content/test/social/opengraph/opengraph.html
new file mode 100644
index 000000000..50b7703b8
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/opengraph.html
@@ -0,0 +1,13 @@
+<html xmlns:og="http://ogp.me/ns#">
+<head>
+ <meta property="og:title" content="&gt;This is my title&lt;"/>
+ <meta property="og:url" content="https://www.mozilla.org"/>
+ <meta property="og:image" content="https://www.mozilla.org/favicon.png"/>
+ <meta property="og:site_name" content="&#62;My simple test page&#60;"/>
+ <meta property="og:description"
+ content="A test corpus file for open graph tags we care about"/>
+</head>
+<body>
+ Open Graph Test Page
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shortlink_linkrel.html b/browser/base/content/test/social/opengraph/shortlink_linkrel.html
new file mode 100644
index 000000000..54c40c376
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shortlink_linkrel.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link rel="shortlink" href="http://imshort/p/abcde" />
+</head>
+<body>
+ link[rel='shortlink']
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shorturl_link.html b/browser/base/content/test/social/opengraph/shorturl_link.html
new file mode 100644
index 000000000..667122cea
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shorturl_link.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link id="shorturl" rev="canonical" type="text/html" href="http://imshort/p/abcde" />
+</head>
+<body>
+ link id="shorturl"
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shorturl_linkrel.html b/browser/base/content/test/social/opengraph/shorturl_linkrel.html
new file mode 100644
index 000000000..36533528e
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shorturl_linkrel.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+ <title>Test Image</title>
+
+ <meta name="description" content="Iron man in a tutu" />
+ <meta name="title" content="Test Image" />
+
+ <meta name="medium" content="image" />
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link id="shorturl" href="http://imshort/p/abcde" />
+
+ <meta property="og:title" content="TestImage" />
+ <meta property="og:type" content="photos:photo" />
+ <meta property="og:url" content="http://www.example.com/photos/56789/" />
+ <meta property="og:site_name" content="My Photo Site" />
+ <meta property="og:description" content="Iron man in a tutu" />
+ <meta property="og:image" content="http://example.com/1234/56789.jpg" />
+ <meta property="og:image:width" content="480" />
+ <meta property="og:image:height" content="640" />
+</head>
+<body>
+ link[rel='shorturl']
+</body>
+</html>
diff --git a/browser/base/content/test/social/share.html b/browser/base/content/test/social/share.html
new file mode 100644
index 000000000..55cba9844
--- /dev/null
+++ b/browser/base/content/test/social/share.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body onload="document.getElementById('testclose').focus()">
+ <p>This is a test social share window.</p>
+ <input id="testclose"/>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/share_activate.html b/browser/base/content/test/social/share_activate.html
new file mode 100644
index 000000000..69707e705
--- /dev/null
+++ b/browser/base/content/test/social/share_activate.html
@@ -0,0 +1,35 @@
+<html>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<head>
+ <title>Activation test</title>
+</head>
+<script>
+
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ // browser_share.js serves this page from "https://example.com"
+ "origin": "https://example.com",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+ "shareURL": "/browser/browser/base/content/test/social/share.html"
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation" onclick="activate(this, true)">Activate the share provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate.html b/browser/base/content/test/social/social_activate.html
new file mode 100644
index 000000000..78da597a1
--- /dev/null
+++ b/browser/base/content/test/social/social_activate.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Activation test</title>
+</head>
+<script>
+// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+
+ // at least one of these must be defined
+ "shareURL": "/browser/browser/base/content/test/social/social_share.html",
+ "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
+
+ // should be available for display purposes
+ "description": "A short paragraph about this provider",
+ "author": "Shane Caraveo, Mozilla",
+
+ // optional
+ "version": "1.0"
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate_basic.html b/browser/base/content/test/social/social_activate_basic.html
new file mode 100644
index 000000000..78da597a1
--- /dev/null
+++ b/browser/base/content/test/social/social_activate_basic.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Activation test</title>
+</head>
+<script>
+// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+
+ // at least one of these must be defined
+ "shareURL": "/browser/browser/base/content/test/social/social_share.html",
+ "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
+
+ // should be available for display purposes
+ "description": "A short paragraph about this provider",
+ "author": "Shane Caraveo, Mozilla",
+
+ // optional
+ "version": "1.0"
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate_iframe.html b/browser/base/content/test/social/social_activate_iframe.html
new file mode 100644
index 000000000..bde884c9d
--- /dev/null
+++ b/browser/base/content/test/social/social_activate_iframe.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <title>Activation iframe test</title>
+</head>
+
+<body>
+
+<iframe src="social_activate_basic.html"/>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_crash_content_helper.js b/browser/base/content/test/social/social_crash_content_helper.js
new file mode 100644
index 000000000..4698b6957
--- /dev/null
+++ b/browser/base/content/test/social/social_crash_content_helper.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Cu = Components.utils;
+
+// Ideally we would use CrashTestUtils.jsm, but that's only available for
+// xpcshell tests - so we just copy a ctypes crasher from it.
+Cu.import("resource://gre/modules/ctypes.jsm");
+var crash = function() { // this will crash when called.
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+};
+
+
+var TestHelper = {
+ init: function() {
+ addMessageListener("social-test:crash", this);
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "social-test:crash":
+ privateNoteIntentionalCrash();
+ crash();
+ break;
+ }
+ },
+}
+
+TestHelper.init();
diff --git a/browser/base/content/test/social/social_postActivation.html b/browser/base/content/test/social/social_postActivation.html
new file mode 100644
index 000000000..e0a6acfdf
--- /dev/null
+++ b/browser/base/content/test/social/social_postActivation.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Post-Activation test</title>
+</head>
+
+<body>
+
+Post Activation landing page
+
+</body>
+</html>
diff --git a/browser/base/content/test/tabPrompts/.eslintrc.js b/browser/base/content/test/tabPrompts/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/tabPrompts/browser.ini b/browser/base/content/test/tabPrompts/browser.ini
new file mode 100644
index 000000000..9b94f14c5
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser.ini
@@ -0,0 +1,4 @@
+[browser_closeTabSpecificPanels.js]
+[browser_multiplePrompts.js]
+[browser_openPromptInBackgroundTab.js]
+support-files = openPromptOffTimeout.html
diff --git a/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
new file mode 100644
index 000000000..30c15a56f
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
@@ -0,0 +1,41 @@
+"use strict";
+
+/*
+ * This test creates multiple panels, one that has been tagged as specific to its tab's content
+ * and one that isn't. When a tab loses focus, panel specific to that tab should close.
+ * The non-specific panel should remain open.
+ *
+ */
+
+add_task(function*() {
+ let tab1 = gBrowser.addTab("http://mochi.test:8888/#0");
+ let tab2 = gBrowser.addTab("http://mochi.test:8888/#1");
+ let specificPanel = document.createElement("panel");
+ specificPanel.setAttribute("tabspecific", "true");
+ let generalPanel = document.createElement("panel");
+ let anchor = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ anchor.appendChild(specificPanel);
+ anchor.appendChild(generalPanel);
+ is(specificPanel.state, "closed", "specificPanel starts as closed");
+ is(generalPanel.state, "closed", "generalPanel starts as closed");
+
+ let specificPanelPromise = BrowserTestUtils.waitForEvent(specificPanel, "popupshown");
+ specificPanel.openPopupAtScreen(210, 210);
+ yield specificPanelPromise;
+ is(specificPanel.state, "open", "specificPanel has been opened");
+
+ let generalPanelPromise = BrowserTestUtils.waitForEvent(generalPanel, "popupshown");
+ generalPanel.openPopupAtScreen(510, 510);
+ yield generalPanelPromise;
+ is(generalPanel.state, "open", "generalPanel has been opened");
+
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(specificPanel.state, "closed", "specificPanel panel is closed after its tab loses focus");
+ is(generalPanel.state, "open", "generalPanel is still open after tab switch");
+
+ specificPanel.remove();
+ generalPanel.remove();
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_multiplePrompts.js b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js
new file mode 100644
index 000000000..c548429ea
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js
@@ -0,0 +1,72 @@
+"use strict";
+
+/*
+ * This test triggers multiple alerts on one single tab, because it"s possible
+ * for web content to do so. The behavior is described in bug 1266353.
+ *
+ * We assert the presentation of the multiple alerts, ensuring we show only
+ * the oldest one.
+ */
+add_task(function*() {
+ const PROMPTCOUNT = 5;
+
+ let contentScript = function() {
+ var i = 5; // contentScript has no access to PROMPTCOUNT.
+ window.addEventListener("message", function() {
+ i--;
+ if (i) {
+ window.postMessage("ping", "*");
+ }
+ alert("Alert countdown #" + i);
+ });
+ window.postMessage("ping", "*");
+ };
+ let url = "data:text/html,<script>(" + encodeURIComponent(contentScript.toSource()) + ")();</script>"
+
+ let promptsOpenedPromise = new Promise(function(resolve) {
+ let unopenedPromptCount = PROMPTCOUNT;
+ Services.obs.addObserver(function observer() {
+ unopenedPromptCount--;
+ if (!unopenedPromptCount) {
+ Services.obs.removeObserver(observer, "tabmodal-dialog-loaded");
+ info("Prompts opened.");
+ resolve();
+ }
+ }, "tabmodal-dialog-loaded", false);
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
+ info("Tab loaded");
+
+ yield promptsOpenedPromise;
+
+ let promptsCount = PROMPTCOUNT;
+ while (promptsCount--) {
+ let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(prompts.length, promptsCount + 1, "There should be " + (promptsCount + 1) + " prompt(s).");
+ // The oldest should be the first.
+ let i = 0;
+ for (let prompt of prompts) {
+ is(prompt.Dialog.args.text, "Alert countdown #" + i, "The #" + i + " alert should be labelled as such.");
+ if (i !== promptsCount) {
+ is(prompt.hidden, true, "This prompt should be hidden.");
+ i++;
+ continue;
+ }
+
+ is(prompt.hidden, false, "The last prompt should not be hidden.");
+ prompt.onButtonClick(0);
+
+ // The click is handled async; wait for an event loop turn for that to
+ // happen.
+ yield new Promise(function(resolve) {
+ Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ }
+ }
+
+ let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(prompts.length, 0, "Prompts should all be dismissed.");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
new file mode 100644
index 000000000..d244d157a
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
@@ -0,0 +1,66 @@
+"use strict";
+
+const ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://example.com/");
+let pageWithAlert = ROOT + "openPromptOffTimeout.html";
+
+registerCleanupFunction(function() {
+ Services.perms.removeAll(makeURI(pageWithAlert));
+});
+
+/*
+ * This test opens a tab that alerts when it is hidden. We then switch away
+ * from the tab, and check that by default the tab is not automatically
+ * re-selected. We also check that a checkbox appears in the alert that allows
+ * the user to enable this automatically re-selecting. We then check that
+ * checking the checkbox does actually enable that behaviour.
+ */
+add_task(function*() {
+ yield SpecialPowers.pushPrefEnv({"set": [["browser.tabs.dontfocusfordialogs", true]]});
+ let firstTab = gBrowser.selectedTab;
+ // load page that opens prompt when page is hidden
+ let openedTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageWithAlert, true);
+ let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute("attention", openedTab, "true");
+ // switch away from that tab again - this triggers the alert.
+ yield BrowserTestUtils.switchTab(gBrowser, firstTab);
+ // ... but that's async on e10s...
+ yield openedTabGotAttentionPromise;
+ // check for attention attribute
+ is(openedTab.getAttribute("attention"), "true", "Tab with alert should have 'attention' attribute.");
+ ok(!openedTab.selected, "Tab with alert should not be selected");
+
+ // switch tab back, and check the checkbox is displayed:
+ yield BrowserTestUtils.switchTab(gBrowser, openedTab);
+ // check the prompt is there, and the extra row is present
+ let prompts = openedTab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(prompts.length, 1, "There should be 1 prompt");
+ let ourPrompt = prompts[0];
+ let row = ourPrompt.querySelector("row");
+ ok(row, "Should have found the row with our checkbox");
+ let checkbox = row.querySelector("checkbox[label*='example.com']");
+ ok(checkbox, "The checkbox should be there");
+ ok(!checkbox.checked, "Checkbox shouldn't be checked");
+ // tick box and accept dialog
+ checkbox.checked = true;
+ ourPrompt.onButtonClick(0);
+ // Wait for that click to actually be handled completely.
+ yield new Promise(function(resolve) {
+ Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ // check permission is set
+ let ps = Services.perms;
+ is(ps.ALLOW_ACTION, ps.testPermission(makeURI(pageWithAlert), "focus-tab-by-prompt"),
+ "Tab switching should now be allowed");
+
+ let openedTabSelectedPromise = BrowserTestUtils.waitForAttribute("selected", openedTab, "true");
+ // switch to other tab again
+ yield BrowserTestUtils.switchTab(gBrowser, firstTab);
+
+ // This is sync in non-e10s, but in e10s we need to wait for this, so yield anyway.
+ // Note that the switchTab promise doesn't actually guarantee anything about *which*
+ // tab ends up as selected when its event fires, so using that here wouldn't work.
+ yield openedTabSelectedPromise;
+ // should be switched back
+ ok(openedTab.selected, "Ta-dah, the other tab should now be selected again!");
+
+ yield BrowserTestUtils.removeTab(openedTab);
+});
diff --git a/browser/base/content/test/tabPrompts/openPromptOffTimeout.html b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html
new file mode 100644
index 000000000..e865c7872
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html
@@ -0,0 +1,10 @@
+<body>
+This page opens an alert box when the page is hidden.
+<script>
+document.addEventListener("visibilitychange", () => {
+ if (document.hidden) {
+ alert("You hid my page!");
+ }
+}, false);
+</script>
+</body>
diff --git a/browser/base/content/test/tabcrashed/browser.ini b/browser/base/content/test/tabcrashed/browser.ini
new file mode 100644
index 000000000..051b40d9f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ head.js
+[browser_shown.js]
+skip-if = !e10s || !crashreporter
+[browser_clearEmail.js]
+skip-if = !e10s || !crashreporter
+[browser_showForm.js]
+skip-if = !e10s || !crashreporter
+[browser_withoutDump.js]
+skip-if = !e10s
+[browser_autoSubmitRequest.js]
+skip-if = !e10s || !crashreporter
diff --git a/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js
new file mode 100644
index 000000000..778331814
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js
@@ -0,0 +1,152 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const AUTOSUBMIT_PREF = "browser.crashReports.unsubmittedCheck.autoSubmit2";
+
+const {TabStateFlusher} =
+ Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+// On debug builds, crashing tabs results in much thinking, which
+// slows down the test and results in intermittent test timeouts,
+// so we'll pump up the expected timeout for this test.
+requestLongerTimeout(2);
+
+/**
+ * Tests that if the user is not configured to autosubmit
+ * backlogged crash reports, that we offer to do that, and
+ * that the user can accept that offer.
+ */
+add_task(function* test_show_form() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [[AUTOSUBMIT_PREF, false]],
+ })
+
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ // Make sure we've flushed the browser messages so that
+ // we can restore it.
+ yield TabStateFlusher.flush(browser);
+
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+
+ // Ensure the request is visible. We can safely reach into
+ // the content since about:tabcrashed is an in-process URL.
+ let requestAutoSubmit = doc.getElementById("requestAutoSubmit");
+ Assert.ok(!requestAutoSubmit.hidden,
+ "Request for autosubmission is visible.");
+
+ // Since the pref is set to false, the checkbox should be
+ // unchecked.
+ let autoSubmit = doc.getElementById("autoSubmit");
+ Assert.ok(!autoSubmit.checked,
+ "Checkbox for autosubmission is not checked.")
+
+ // Check the checkbox, and then restore the tab.
+ autoSubmit.checked = true;
+ let restoreButton = doc.getElementById("restoreTab");
+ restoreButton.click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+
+ // The autosubmission pref should now be set.
+ Assert.ok(Services.prefs.getBoolPref(AUTOSUBMIT_PREF),
+ "Autosubmission pref should have been set.");
+ });
+});
+
+/**
+ * Tests that if the user is autosubmitting backlogged crash reports
+ * that we don't make the offer again.
+ */
+add_task(function* test_show_form() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [[AUTOSUBMIT_PREF, true]],
+ })
+
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ yield TabStateFlusher.flush(browser);
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+
+ // Ensure the request is NOT visible. We can safely reach into
+ // the content since about:tabcrashed is an in-process URL.
+ let requestAutoSubmit = doc.getElementById("requestAutoSubmit");
+ Assert.ok(requestAutoSubmit.hidden,
+ "Request for autosubmission is not visible.");
+
+ // Restore the tab.
+ let restoreButton = doc.getElementById("restoreTab");
+ restoreButton.click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+
+ // The autosubmission pref should still be set to true.
+ Assert.ok(Services.prefs.getBoolPref(AUTOSUBMIT_PREF),
+ "Autosubmission pref should have been set.");
+ });
+});
+
+/**
+ * Tests that we properly set the autoSubmit preference if the user is
+ * presented with a tabcrashed page without a crash report.
+ */
+add_task(function* test_no_offer() {
+ // We should default to sending the report.
+ Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport"));
+
+ yield SpecialPowers.pushPrefEnv({
+ set: [[AUTOSUBMIT_PREF, false]],
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ yield TabStateFlusher.flush(browser);
+
+ // Make it so that it seems like no dump is available for the next crash.
+ prepareNoDump();
+
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ // eslint-disable-next-line mozilla/no-cpows-in-tests
+ let doc = browser.contentDocument;
+
+ // Ensure the request to autosubmit is invisible, since there's no report.
+ let requestRect = doc.getElementById("requestAutoSubmit")
+ .getBoundingClientRect();
+ Assert.equal(0, requestRect.height,
+ "Request for autosubmission has no height");
+ Assert.equal(0, requestRect.width,
+ "Request for autosubmission has no width");
+
+ // Since the pref is set to false, the checkbox should be
+ // unchecked.
+ let autoSubmit = doc.getElementById("autoSubmit");
+ Assert.ok(!autoSubmit.checked,
+ "Checkbox for autosubmission is not checked.");
+
+ let restoreButton = doc.getElementById("restoreTab");
+ restoreButton.click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+
+ // The autosubmission pref should now be set.
+ Assert.ok(!Services.prefs.getBoolPref(AUTOSUBMIT_PREF),
+ "Autosubmission pref should not have changed.");
+ });
+
+ // We should not have changed the default value for sending the report.
+ Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport"));
+});
diff --git a/browser/base/content/test/tabcrashed/browser_clearEmail.js b/browser/base/content/test/tabcrashed/browser_clearEmail.js
new file mode 100644
index 000000000..9ec04944f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_clearEmail.js
@@ -0,0 +1,85 @@
+"use strict";
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const EMAIL = "foo@privacy.com";
+
+/**
+ * Sets up the browser to send crash reports to the local crash report
+ * testing server.
+ */
+add_task(function* setup() {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash
+ // reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ // By default, requesting the email address of the user is disabled.
+ // For the purposes of this test, we turn it back on.
+ yield SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.crashReporting.requestEmail", true]],
+ });
+
+ registerCleanupFunction(function() {
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ });
+});
+
+/**
+ * Test that if we have an email address stored in prefs, and we decide
+ * not to submit the email address in the next crash report, that we
+ * clear the email address.
+ */
+add_task(function* test_clear_email() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let prefs = TabCrashHandler.prefs;
+ let originalSendReport = prefs.getBoolPref("sendReport");
+ let originalEmailMe = prefs.getBoolPref("emailMe");
+ let originalIncludeURL = prefs.getBoolPref("includeURL");
+ let originalEmail = prefs.getCharPref("email");
+
+ // Pretend that we stored an email address from the previous
+ // crash
+ prefs.setCharPref("email", EMAIL);
+ prefs.setBoolPref("emailMe", true);
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+ let doc = browser.contentDocument;
+
+ // Since about:tabcrashed will run in the parent process, we can safely
+ // manipulate its DOM nodes directly
+ let emailMe = doc.getElementById("emailMe");
+ emailMe.checked = false;
+
+ let crashReport = promiseCrashReport({
+ Email: "",
+ });
+
+ let restoreTab = browser.contentDocument.getElementById("restoreTab");
+ restoreTab.click();
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+ yield crashReport;
+
+ is(prefs.getCharPref("email"), "", "No email address should be stored");
+
+ // Submitting the crash report may have set some prefs regarding how to
+ // send tab crash reports. Let's reset them for the next test.
+ prefs.setBoolPref("sendReport", originalSendReport);
+ prefs.setBoolPref("emailMe", originalEmailMe);
+ prefs.setBoolPref("includeURL", originalIncludeURL);
+ prefs.setCharPref("email", originalEmail);
+ });
+});
+
diff --git a/browser/base/content/test/tabcrashed/browser_showForm.js b/browser/base/content/test/tabcrashed/browser_showForm.js
new file mode 100644
index 000000000..780af93fb
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_showForm.js
@@ -0,0 +1,40 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+// On debug builds, crashing tabs results in much thinking, which
+// slows down the test and results in intermittent test timeouts,
+// so we'll pump up the expected timeout for this test.
+requestLongerTimeout(2);
+
+/**
+ * Tests that we show the about:tabcrashed additional details form
+ * if the "submit a crash report" checkbox was checked by default.
+ */
+add_task(function* test_show_form() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ // Flip the pref so that the checkbox should be checked
+ // by default.
+ let pref = TabCrashHandler.prefs.root + "sendReport";
+ yield SpecialPowers.pushPrefEnv({
+ set: [[pref, true]]
+ });
+
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+
+ // Ensure the checkbox is checked. We can safely reach into
+ // the content since about:tabcrashed is an in-process URL.
+ let checkbox = doc.getElementById("sendReport");
+ ok(checkbox.checked, "Send report checkbox is checked.");
+
+ // Ensure the options form is displayed.
+ let options = doc.getElementById("options");
+ ok(!options.hidden, "Showing the crash report options form.");
+ });
+});
diff --git a/browser/base/content/test/tabcrashed/browser_shown.js b/browser/base/content/test/tabcrashed/browser_shown.js
new file mode 100644
index 000000000..d09d9438f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_shown.js
@@ -0,0 +1,203 @@
+"use strict";
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const COMMENTS = "Here's my test comment!";
+const EMAIL = "foo@privacy.com";
+
+/**
+ * Sets up the browser to send crash reports to the local crash report
+ * testing server.
+ */
+add_task(function* setup() {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash
+ // reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ // On debug builds, crashing tabs results in much thinking, which
+ // slows down the test and results in intermittent test timeouts,
+ // so we'll pump up the expected timeout for this test.
+ requestLongerTimeout(2);
+
+ registerCleanupFunction(function() {
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ });
+});
+
+/**
+ * This function returns a Promise that resolves once the following
+ * actions have taken place:
+ *
+ * 1) A new tab is opened up at PAGE
+ * 2) The tab is crashed
+ * 3) The about:tabcrashed page's fields are set in accordance with
+ * fieldValues
+ * 4) The tab is restored
+ * 5) A crash report is received from the testing server
+ * 6) Any tab crash prefs that were overwritten are reset
+ *
+ * @param fieldValues
+ * An Object describing how to set the about:tabcrashed
+ * fields. The following properties are accepted:
+ *
+ * comments (String)
+ * The comments to put in the comment textarea
+ * email (String)
+ * The email address to put in the email address input
+ * emailMe (bool)
+ * The checked value of the "Email me" checkbox
+ * includeURL (bool)
+ * The checked value of the "Include URL" checkbox
+ *
+ * If any of these fields are missing, the defaults from
+ * the user preferences are used.
+ * @param expectedExtra
+ * An Object describing the expected values that the submitted
+ * crash report's extra data should contain.
+ * @returns Promise
+ */
+function crashTabTestHelper(fieldValues, expectedExtra) {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let prefs = TabCrashHandler.prefs;
+ let originalSendReport = prefs.getBoolPref("sendReport");
+ let originalEmailMe = prefs.getBoolPref("emailMe");
+ let originalIncludeURL = prefs.getBoolPref("includeURL");
+ let originalEmail = prefs.getCharPref("email");
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+ let doc = browser.contentDocument;
+
+ // Since about:tabcrashed will run in the parent process, we can safely
+ // manipulate its DOM nodes directly
+ let comments = doc.getElementById("comments");
+ let email = doc.getElementById("email");
+ let emailMe = doc.getElementById("emailMe");
+ let includeURL = doc.getElementById("includeURL");
+
+ if (fieldValues.hasOwnProperty("comments")) {
+ comments.value = fieldValues.comments;
+ }
+
+ if (fieldValues.hasOwnProperty("email")) {
+ email.value = fieldValues.email;
+ }
+
+ if (fieldValues.hasOwnProperty("emailMe")) {
+ emailMe.checked = fieldValues.emailMe;
+ }
+
+ if (fieldValues.hasOwnProperty("includeURL")) {
+ includeURL.checked = fieldValues.includeURL;
+ }
+
+ let crashReport = promiseCrashReport(expectedExtra);
+ let restoreTab = browser.contentDocument.getElementById("restoreTab");
+ restoreTab.click();
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+ yield crashReport;
+
+ // Submitting the crash report may have set some prefs regarding how to
+ // send tab crash reports. Let's reset them for the next test.
+ prefs.setBoolPref("sendReport", originalSendReport);
+ prefs.setBoolPref("emailMe", originalEmailMe);
+ prefs.setBoolPref("includeURL", originalIncludeURL);
+ prefs.setCharPref("email", originalEmail);
+ });
+}
+
+/**
+ * Tests what we send with the crash report by default. By default, we do not
+ * send any comments, the URL of the crashing page, or the email address of
+ * the user.
+ */
+add_task(function* test_default() {
+ yield crashTabTestHelper({}, {
+ "Comments": null,
+ "URL": "",
+ "Email": null,
+ });
+});
+
+/**
+ * Test just sending a comment.
+ */
+add_task(function* test_just_a_comment() {
+ yield crashTabTestHelper({
+ comments: COMMENTS,
+ }, {
+ "Comments": COMMENTS,
+ "URL": "",
+ "Email": null,
+ });
+});
+
+/**
+ * Test that we don't send email if emailMe is unchecked
+ */
+add_task(function* test_no_email() {
+ yield crashTabTestHelper({
+ email: EMAIL,
+ emailMe: false,
+ }, {
+ "Comments": null,
+ "URL": "",
+ "Email": null,
+ });
+});
+
+/**
+ * Test that we can send an email address if emailMe is checked
+ */
+add_task(function* test_yes_email() {
+ yield crashTabTestHelper({
+ email: EMAIL,
+ emailMe: true,
+ }, {
+ "Comments": null,
+ "URL": "",
+ "Email": EMAIL,
+ });
+});
+
+/**
+ * Test that we will send the URL of the page if includeURL is checked.
+ */
+add_task(function* test_send_URL() {
+ yield crashTabTestHelper({
+ includeURL: true,
+ }, {
+ "Comments": null,
+ "URL": PAGE,
+ "Email": null,
+ });
+});
+
+/**
+ * Test that we can send comments, the email address, and the URL
+ */
+add_task(function* test_send_all() {
+ yield crashTabTestHelper({
+ includeURL: true,
+ emailMe: true,
+ email: EMAIL,
+ comments: COMMENTS,
+ }, {
+ "Comments": COMMENTS,
+ "URL": PAGE,
+ "Email": EMAIL,
+ });
+});
+
diff --git a/browser/base/content/test/tabcrashed/browser_withoutDump.js b/browser/base/content/test/tabcrashed/browser_withoutDump.js
new file mode 100644
index 000000000..62557f443
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_withoutDump.js
@@ -0,0 +1,36 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+add_task(function* setup() {
+ prepareNoDump();
+});
+
+/**
+ * Tests tab crash page when a dump is not available.
+ */
+add_task(function* test_without_dump() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let tabRemovedPromise = BrowserTestUtils.removeTab(tab, { dontRemove: true });
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let doc = content.document;
+ Assert.ok(!doc.documentElement.classList.contains("crashDumpAvailable"),
+ "doesn't have crash dump");
+
+ let options = doc.getElementById("options");
+ Assert.ok(options, "has crash report options");
+ Assert.ok(options.hidden, "crash report options are hidden");
+
+ doc.getElementById("closeTab").click();
+ });
+
+ yield tabRemovedPromise;
+ });
+});
diff --git a/browser/base/content/test/tabcrashed/head.js b/browser/base/content/test/tabcrashed/head.js
new file mode 100644
index 000000000..6eee08f13
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/head.js
@@ -0,0 +1,110 @@
+/**
+ * Returns a Promise that resolves once a crash report has
+ * been submitted. This function will also test the crash
+ * reports extra data to see if it matches expectedExtra.
+ *
+ * @param expectedExtra (object)
+ * An Object whose key-value pairs will be compared
+ * against the key-value pairs in the extra data of the
+ * crash report. A test failure will occur if there is
+ * a mismatch.
+ *
+ * If the value of the key-value pair is "null", this will
+ * be interpreted as "this key should not be included in the
+ * extra data", and will cause a test failure if it is detected
+ * in the crash report.
+ *
+ * Note that this will ignore any keys that are not included
+ * in expectedExtra. It's possible that the crash report
+ * will contain other extra information that is not
+ * compared against.
+ * @returns Promise
+ */
+function promiseCrashReport(expectedExtra={}) {
+ return Task.spawn(function*() {
+ info("Starting wait on crash-report-status");
+ let [subject, ] =
+ yield TestUtils.topicObserved("crash-report-status", (unused, data) => {
+ return data == "success";
+ });
+ info("Topic observed!");
+
+ if (!(subject instanceof Ci.nsIPropertyBag2)) {
+ throw new Error("Subject was not a Ci.nsIPropertyBag2");
+ }
+
+ let remoteID = getPropertyBagValue(subject, "serverCrashID");
+ if (!remoteID) {
+ throw new Error("Report should have a server ID");
+ }
+
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(remoteID + ".txt");
+ if (!file.exists()) {
+ throw new Error("Report should have been received by the server");
+ }
+
+ file.remove(false);
+
+ let extra = getPropertyBagValue(subject, "extra");
+ if (!(extra instanceof Ci.nsIPropertyBag2)) {
+ throw new Error("extra was not a Ci.nsIPropertyBag2");
+ }
+
+ info("Iterating crash report extra keys");
+ let enumerator = extra.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let key = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
+ let value = extra.getPropertyAsAString(key);
+ if (key in expectedExtra) {
+ if (expectedExtra[key] == null) {
+ ok(false, `Got unexpected key ${key} with value ${value}`);
+ } else {
+ is(value, expectedExtra[key],
+ `Crash report had the right extra value for ${key}`);
+ }
+ }
+ }
+ });
+}
+
+
+/**
+ * For an nsIPropertyBag, returns the value for a given
+ * key.
+ *
+ * @param bag
+ * The nsIPropertyBag to retrieve the value from
+ * @param key
+ * The key that we want to get the value for from the
+ * bag
+ * @returns The value corresponding to the key from the bag,
+ * or null if the value could not be retrieved (for
+ * example, if no value is set at that key).
+*/
+function getPropertyBagValue(bag, key) {
+ try {
+ let val = bag.getProperty(key);
+ return val;
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Monkey patches TabCrashHandler.getDumpID to return null in order to test
+ * about:tabcrashed when a dump is not available.
+ */
+function prepareNoDump() {
+ let originalGetDumpID = TabCrashHandler.getDumpID;
+ TabCrashHandler.getDumpID = function(browser) { return null; };
+ registerCleanupFunction(() => {
+ TabCrashHandler.getDumpID = originalGetDumpID;
+ });
+}
diff --git a/browser/base/content/test/tabs/.eslintrc.js b/browser/base/content/test/tabs/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/tabs/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini
new file mode 100644
index 000000000..7771e0a6e
--- /dev/null
+++ b/browser/base/content/test/tabs/browser.ini
@@ -0,0 +1,4 @@
+[browser_tabSpinnerProbe.js]
+skip-if = !e10s # Tab spinner is e10s only.
+[browser_tabSwitchPrintPreview.js]
+skip-if = os == 'mac'
diff --git a/browser/base/content/test/tabs/browser_tabSpinnerProbe.js b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
new file mode 100644
index 000000000..c3569c2b1
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
@@ -0,0 +1,93 @@
+"use strict";
+
+/**
+ * Tests the FX_TAB_SWITCH_SPINNER_VISIBLE_MS and
+ * FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS telemetry probes
+ */
+let gMinHangTime = 500; // ms
+let gMaxHangTime = 5 * 1000; // ms
+
+/**
+ * Make a data URI for a generic webpage with a script that hangs for a given
+ * amount of time.
+ * @param {?Number} aHangMs Number of milliseconds that the hang should last.
+ * Defaults to 0.
+ * @return {String} The data URI generated.
+ */
+function makeDataURI(aHangMs = 0) {
+ return `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tab Spinner Test</title>
+ <script>
+ function hang() {
+ let hangDuration = ${aHangMs};
+ if (hangDuration > 0) {
+ let startTime = window.performance.now();
+ while(window.performance.now() - startTime < hangDuration) {}
+ }
+ }
+ </script>
+ </head>
+ <body>
+ <h1 id='header'>Tab Spinner Test</h1>
+ </body>
+ </html>`;
+}
+
+/**
+ * Returns the sum of all values in an array.
+ * @param {Array} aArray An array of integers
+ * @return {Number} The sum of the integers in the array
+ */
+function sum(aArray) {
+ return aArray.reduce(function(previousValue, currentValue) {
+ return previousValue + currentValue;
+ });
+}
+
+/**
+ * A generator intended to be run as a Task. It tests one of the tab spinner
+ * telemetry probes.
+ * @param {String} aProbe The probe to test. Should be one of:
+ * - FX_TAB_SWITCH_SPINNER_VISIBLE_MS
+ * - FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS
+ */
+function* testProbe(aProbe) {
+ info(`Testing probe: ${aProbe}`);
+ let histogram = Services.telemetry.getHistogramById(aProbe);
+ let buckets = histogram.snapshot().ranges.filter(function(value) {
+ return (value > gMinHangTime && value < gMaxHangTime);
+ });
+ let delayTime = buckets[0]; // Pick a bucket arbitrarily
+
+ // The tab spinner does not show up instantly. We need to hang for a little
+ // bit of extra time to account for the tab spinner delay.
+ delayTime += gBrowser.selectedTab.linkedBrowser.getTabBrowser()._getSwitcher().TAB_SWITCH_TIMEOUT;
+ let dataURI1 = makeDataURI(delayTime);
+ let dataURI2 = makeDataURI();
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI1);
+ histogram.clear();
+ // Queue a hang in the content process when the
+ // event loop breathes next.
+ ContentTask.spawn(tab1.linkedBrowser, null, function*() {
+ content.wrappedJSObject.hang();
+ });
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI2);
+ let snapshot = histogram.snapshot();
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab1);
+ ok(sum(snapshot.counts) > 0,
+ `Spinner probe should now have a value in some bucket`);
+}
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_MS"));
+add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS"));
diff --git a/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js
new file mode 100644
index 000000000..4ec36a7cc
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js
@@ -0,0 +1,29 @@
+const kURL1 = "data:text/html,Should I stay or should I go?";
+const kURL2 = "data:text/html,I shouldn't be here!";
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+/**
+ * Verify that if we open a new tab and try to make it the selected tab while
+ * print preview is up, that doesn't happen.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab(kURL1, function* (browser) {
+ let tab = gBrowser.addTab(kURL2);
+ document.getElementById("cmd_printPreview").doCommand();
+ gBrowser.selectedTab = tab;
+ yield BrowserTestUtils.waitForCondition(() => gInPrintPreviewMode, "should be in print preview mode");
+ isnot(gBrowser.selectedTab, tab, "Selected tab should not be the tab we added");
+ is(gBrowser.selectedTab, PrintPreviewListener._printPreviewTab, "Selected tab should be the print preview tab");
+ gBrowser.selectedTab = tab;
+ isnot(gBrowser.selectedTab, tab, "Selected tab should still not be the tab we added");
+ is(gBrowser.selectedTab, PrintPreviewListener._printPreviewTab, "Selected tab should still be the print preview tab");
+ PrintUtils.exitPrintPreview();
+ yield BrowserTestUtils.waitForCondition(() => !gInPrintPreviewMode, "should be in print preview mode");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/browser/base/content/test/urlbar/.eslintrc.js b/browser/base/content/test/urlbar/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/urlbar/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/urlbar/authenticate.sjs b/browser/base/content/test/urlbar/authenticate.sjs
new file mode 100644
index 000000000..58da655cf
--- /dev/null
+++ b/browser/base/content/test/urlbar/authenticate.sjs
@@ -0,0 +1,220 @@
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 200, "AlmostOK");
+ response.write("Error handling request: " + e);
+ }
+}
+
+
+function reallyHandleRequest(request, response) {
+ var match;
+ var requestAuth = true, requestProxyAuth = true;
+
+ // Allow the caller to drive how authentication is processed via the query.
+ // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
+ // The extra ? allows the user/pass/realm checks to succeed if the name is
+ // at the beginning of the query string.
+ var query = "?" + request.queryString;
+
+ var expected_user = "", expected_pass = "", realm = "mochitest";
+ var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
+ var huge = false, plugin = false, anonymous = false;
+ var authHeaderCount = 1;
+ // user=xxx
+ match = /[^_]user=([^&]*)/.exec(query);
+ if (match)
+ expected_user = match[1];
+
+ // pass=xxx
+ match = /[^_]pass=([^&]*)/.exec(query);
+ if (match)
+ expected_pass = match[1];
+
+ // realm=xxx
+ match = /[^_]realm=([^&]*)/.exec(query);
+ if (match)
+ realm = match[1];
+
+ // proxy_user=xxx
+ match = /proxy_user=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_user = match[1];
+
+ // proxy_pass=xxx
+ match = /proxy_pass=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_pass = match[1];
+
+ // proxy_realm=xxx
+ match = /proxy_realm=([^&]*)/.exec(query);
+ if (match)
+ proxy_realm = match[1];
+
+ // huge=1
+ match = /huge=1/.exec(query);
+ if (match)
+ huge = true;
+
+ // plugin=1
+ match = /plugin=1/.exec(query);
+ if (match)
+ plugin = true;
+
+ // multiple=1
+ match = /multiple=([^&]*)/.exec(query);
+ if (match)
+ authHeaderCount = match[1]+0;
+
+ // anonymous=1
+ match = /anonymous=1/.exec(query);
+ if (match)
+ anonymous = true;
+
+ // Look for an authentication header, if any, in the request.
+ //
+ // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ //
+ // This test only supports Basic auth. The value sent by the client is
+ // "username:password", obscured with base64 encoding.
+
+ var actual_user = "", actual_pass = "", authHeader, authPresent = false;
+ if (request.hasHeader("Authorization")) {
+ authPresent = true;
+ authHeader = request.getHeader("Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ actual_user = match[1];
+ actual_pass = match[2];
+ }
+
+ var proxy_actual_user = "", proxy_actual_pass = "";
+ if (request.hasHeader("Proxy-Authorization")) {
+ authHeader = request.getHeader("Proxy-Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ proxy_actual_user = match[1];
+ proxy_actual_pass = match[2];
+ }
+
+ // Don't request authentication if the credentials we got were what we
+ // expected.
+ if (expected_user == actual_user &&
+ expected_pass == actual_pass) {
+ requestAuth = false;
+ }
+ if (proxy_expected_user == proxy_actual_user &&
+ proxy_expected_pass == proxy_actual_pass) {
+ requestProxyAuth = false;
+ }
+
+ if (anonymous) {
+ if (authPresent) {
+ response.setStatusLine("1.0", 400, "Unexpected authorization header found");
+ } else {
+ response.setStatusLine("1.0", 200, "Authorization header not found");
+ }
+ } else {
+ if (requestProxyAuth) {
+ response.setStatusLine("1.0", 407, "Proxy authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
+ } else if (requestAuth) {
+ response.setStatusLine("1.0", 401, "Authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+ } else {
+ response.setStatusLine("1.0", 200, "OK");
+ }
+ }
+
+ response.setHeader("Content-Type", "application/xhtml+xml", false);
+ response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
+ response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
+ response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
+ response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
+
+ if (huge) {
+ response.write("<div style='display: none'>");
+ for (i = 0; i < 100000; i++) {
+ response.write("123456789\n");
+ }
+ response.write("</div>");
+ response.write("<span id='footnote'>This is a footnote after the huge content fill</span>");
+ }
+
+ if (plugin) {
+ response.write("<embed id='embedtest' style='width: 400px; height: 100px;' " +
+ "type='application/x-test'></embed>\n");
+ }
+
+ response.write("</html>");
+}
+
+
+// base64 decoder
+//
+// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa()
+// doesn't seem to exist. :-(
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+
+ var result = '';
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+
+ // Convert one by one.
+ for (var i = 0; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data[i] == base64Pad);
+ // Skip illegal characters and whitespace
+ if (c == -1) continue;
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding)
+ result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits)
+ throw Components.Exception('Corrupted base64 string');
+
+ return result;
+}
diff --git a/browser/base/content/test/urlbar/browser.ini b/browser/base/content/test/urlbar/browser.ini
new file mode 100644
index 000000000..39bc086c9
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -0,0 +1,101 @@
+[DEFAULT]
+support-files =
+ dummy_page.html
+ head.js
+
+[browser_URLBarSetURI.js]
+skip-if = (os == "linux" || os == "mac") && debug # bug 970052, bug 970053
+[browser_action_keyword.js]
+skip-if = os == "linux" # Bug 1188154
+support-files =
+ print_postdata.sjs
+[browser_action_keyword_override.js]
+[browser_action_searchengine.js]
+[browser_action_searchengine_alias.js]
+[browser_autocomplete_a11y_label.js]
+[browser_autocomplete_autoselect.js]
+[browser_autocomplete_cursor.js]
+[browser_autocomplete_edit_completed.js]
+[browser_autocomplete_enter_race.js]
+[browser_autocomplete_no_title.js]
+[browser_autocomplete_tag_star_visibility.js]
+[browser_bug1104165-switchtab-decodeuri.js]
+[browser_bug1003461-switchtab-override.js]
+[browser_bug1024133-switchtab-override-keynav.js]
+[browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
+[browser_bug1070778.js]
+[browser_bug1225194-remotetab.js]
+[browser_bug304198.js]
+[browser_bug556061.js]
+subsuite = clipboard
+[browser_bug562649.js]
+[browser_bug623155.js]
+support-files =
+ redirect_bug623155.sjs
+[browser_bug783614.js]
+[browser_canonizeURL.js]
+[browser_dragdropURL.js]
+[browser_locationBarCommand.js]
+[browser_locationBarExternalLoad.js]
+[browser_moz_action_link.js]
+[browser_removeUnsafeProtocolsFromURLBarPaste.js]
+subsuite = clipboard
+[browser_search_favicon.js]
+[browser_tabMatchesInAwesomebar.js]
+support-files =
+ moz.png
+[browser_tabMatchesInAwesomebar_perwindowpb.js]
+skip-if = os == 'linux' # Bug 1104755
+[browser_urlbarAboutHomeLoading.js]
+[browser_urlbarAutoFillTrimURLs.js]
+[browser_urlbarCopying.js]
+subsuite = clipboard
+support-files =
+ authenticate.sjs
+[browser_urlbarDecode.js]
+[browser_urlbarDelete.js]
+[browser_urlbarEnter.js]
+[browser_urlbarEnterAfterMouseOver.js]
+skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
+[browser_urlbarFocusedCmdK.js]
+[browser_urlbarHashChangeProxyState.js]
+[browser_urlbarKeepStateAcrossTabSwitches.js]
+[browser_urlbarOneOffs.js]
+[browser_urlbarPrivateBrowsingWindowChange.js]
+[browser_urlbarRaceWithTabs.js]
+[browser_urlbarRevert.js]
+[browser_urlbarSearchSingleWordNotification.js]
+[browser_urlbarSearchSuggestions.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbarSearchSuggestionsNotification.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbarSearchTelemetry.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbarStop.js]
+[browser_urlbarTrimURLs.js]
+subsuite = clipboard
+[browser_urlbarUpdateForDomainCompletion.js]
+[browser_urlbar_autoFill_backspaced.js]
+[browser_urlbar_blanking.js]
+support-files =
+ file_blank_but_not_blank.html
+[browser_urlbar_locationchange_urlbar_edit_dos.js]
+support-files =
+ file_urlbar_edit_dos.html
+[browser_urlbar_searchsettings.js]
+[browser_urlbar_stop_pending.js]
+support-files =
+ slow-page.sjs
+[browser_urlbar_remoteness_switch.js]
+run-if = e10s
+[browser_urlHighlight.js]
+[browser_wyciwyg_urlbarCopying.js]
+subsuite = clipboard
+support-files =
+ test_wyciwyg_copying.html
diff --git a/browser/base/content/test/urlbar/browser_URLBarSetURI.js b/browser/base/content/test/urlbar/browser_URLBarSetURI.js
new file mode 100644
index 000000000..ac8352f1a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_URLBarSetURI.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // avoid prompting about phishing
+ Services.prefs.setIntPref(phishyUserPassPref, 32);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ });
+
+ nextTest();
+}
+
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function nextTest() {
+ let test = tests.shift();
+ if (test) {
+ test(function () {
+ executeSoon(nextTest);
+ });
+ } else {
+ executeSoon(finish);
+ }
+}
+
+var tests = [
+ function revert(next) {
+ loadTabInWindow(window, function (tab) {
+ gURLBar.handleRevert();
+ is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after reverting");
+ gBrowser.removeTab(tab);
+ next();
+ });
+ },
+ function customize(next) {
+ // Need to wait for delayedStartup for the customization part of the test,
+ // since that's where BrowserToolboxCustomizeDone is set.
+ BrowserTestUtils.openNewBrowserWindow().then(function(win) {
+ loadTabInWindow(win, function () {
+ openToolbarCustomizationUI(function () {
+ closeToolbarCustomizationUI(function () {
+ is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped after customize");
+ win.close();
+ next();
+ }, win);
+ }, win);
+ });
+ });
+ },
+ function pageloaderror(next) {
+ loadTabInWindow(window, function (tab) {
+ // Load a new URL and then immediately stop it, to simulate a page load
+ // error.
+ tab.linkedBrowser.loadURI("http://test1.example.com");
+ tab.linkedBrowser.stop();
+ is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after load error");
+ gBrowser.removeTab(tab);
+ next();
+ });
+ }
+];
+
+function loadTabInWindow(win, callback) {
+ info("Loading tab");
+ let url = "http://user:pass@example.com/";
+ let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(url);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url).then(() => {
+ info("Tab loaded");
+ is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped initially");
+ callback(tab);
+ }, true);
+}
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+ if (!aBrowserWin)
+ aBrowserWin = window;
+
+ aBrowserWin.gCustomizeMode.enter();
+
+ aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
+ executeSoon(function() {
+ aCallback(aBrowserWin)
+ });
+ });
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+ aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
+ executeSoon(aCallback);
+ });
+
+ aBrowserWin.gCustomizeMode.exit();
+}
+
diff --git a/browser/base/content/test/urlbar/browser_action_keyword.js b/browser/base/content/test/urlbar/browser_action_keyword.js
new file mode 100644
index 000000000..854a7b82f
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_keyword.js
@@ -0,0 +1,119 @@
+function* promise_first_result(inputText) {
+ yield promiseAutocompleteResultPopup(inputText);
+
+ let firstResult = gURLBar.popup.richlistbox.firstChild;
+ return firstResult;
+}
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/print_postdata.sjs";
+
+add_task(function* setup() {
+ yield PlacesUtils.keywords.insert({ keyword: "get",
+ url: TEST_URL + "?q=%s" });
+ yield PlacesUtils.keywords.insert({ keyword: "post",
+ url: TEST_URL,
+ postData: "q=%s" });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.keywords.remove("get");
+ yield PlacesUtils.keywords.remove("post");
+ while (gBrowser.tabs.length > 1) {
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ });
+});
+
+add_task(function* get_keyword() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ let result = yield promise_first_result("get something");
+ isnot(result, null, "Expect a keyword result");
+
+ let types = new Set(result.getAttribute("type").split(/\s+/));
+ Assert.ok(types.has("keyword"));
+ is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute");
+ is(result.getAttribute("title"), "mochi.test:8888", "Expect correct title");
+
+ // We need to make a real URI out of this to ensure it's normalised for
+ // comparison.
+ let uri = NetUtil.newURI(result.getAttribute("url"));
+ is(uri.spec, PlacesUtils.mozActionURI("keyword",
+ { url: TEST_URL + "?q=something",
+ input: "get something"}),
+ "Expect correct url");
+
+ let titleHbox = result._titleText.parentNode.parentNode;
+ ok(titleHbox.classList.contains("ac-title"), "Title hbox element sanity check");
+ is_element_visible(titleHbox, "Title element should be visible");
+ is(result._titleText.textContent, "mochi.test:8888: something",
+ "Node should contain the name of the bookmark and query");
+
+ let urlHbox = result._urlText.parentNode.parentNode;
+ ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+
+ let actionHbox = result._actionText.parentNode.parentNode;
+ ok(actionHbox.classList.contains("ac-action"), "Action hbox element sanity check");
+ is_element_visible(actionHbox, "Action element should be visible");
+ is(result._actionText.textContent, "", "Action text should be empty");
+
+ // Click on the result
+ info("Normal click on result");
+ let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeMouseAtCenter(result, {});
+ yield tabPromise;
+ is(tab.linkedBrowser.currentURI.spec, TEST_URL + "?q=something",
+ "Tab should have loaded from clicking on result");
+
+ // Middle-click on the result
+ info("Middle-click on result");
+ result = yield promise_first_result("get somethingmore");
+ isnot(result, null, "Expect a keyword result");
+ // We need to make a real URI out of this to ensure it's normalised for
+ // comparison.
+ uri = NetUtil.newURI(result.getAttribute("url"));
+ is(uri.spec, PlacesUtils.mozActionURI("keyword",
+ { url: TEST_URL + "?q=somethingmore",
+ input: "get somethingmore" }),
+ "Expect correct url");
+
+ tabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ EventUtils.synthesizeMouseAtCenter(result, {button: 1});
+ let tabOpenEvent = yield tabPromise;
+ let newTab = tabOpenEvent.target;
+ yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+ is(newTab.linkedBrowser.currentURI.spec,
+ TEST_URL + "?q=somethingmore",
+ "Tab should have loaded from middle-clicking on result");
+});
+
+
+add_task(function* post_keyword() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ let result = yield promise_first_result("post something");
+ isnot(result, null, "Expect a keyword result");
+
+ let types = new Set(result.getAttribute("type").split(/\s+/));
+ Assert.ok(types.has("keyword"));
+ is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute");
+ is(result.getAttribute("title"), "mochi.test:8888", "Expect correct title");
+
+ is(result.getAttribute("url"),
+ PlacesUtils.mozActionURI("keyword", { url: TEST_URL,
+ input: "post something",
+ "postData": "q=something" }),
+ "Expect correct url");
+
+ // Click on the result
+ info("Normal click on result");
+ let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeMouseAtCenter(result, {});
+ yield tabPromise;
+ is(tab.linkedBrowser.currentURI.spec, TEST_URL,
+ "Tab should have loaded from clicking on result");
+
+ let postData = yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
+ return content.document.body.textContent;
+ });
+ is(postData, "q=something", "post data was submitted correctly");
+});
diff --git a/browser/base/content/test/urlbar/browser_action_keyword_override.js b/browser/base/content/test/urlbar/browser_action_keyword_override.js
new file mode 100644
index 000000000..f5a865678
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_keyword_override.js
@@ -0,0 +1,40 @@
+add_task(function*() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test" });
+ yield PlacesUtils.keywords.insert({ keyword: "keyword",
+ url: "http://example.com/?q=%s" })
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+
+ yield promiseAutocompleteResultPopup("keyword search");
+ let result = gURLBar.popup.richlistbox.children[0];
+
+ info("Before override");
+ let titleHbox = result._titleText.parentNode.parentNode;
+ ok(titleHbox.classList.contains("ac-title"), "Title hbox element sanity check");
+ is_element_visible(titleHbox, "Title element should be visible");
+
+ let urlHbox = result._urlText.parentNode.parentNode;
+ ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+
+ let actionHbox = result._actionText.parentNode.parentNode;
+ ok(actionHbox.classList.contains("ac-action"), "Action hbox element sanity check");
+ is_element_visible(actionHbox, "Action element should be visible");
+ is(result._actionText.textContent, "", "Action text should be empty");
+
+ info("During override");
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+ is_element_visible(titleHbox, "Title element should be visible");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+ is_element_visible(actionHbox, "Action element should be visible");
+ is(result._actionText.textContent, "", "Action text should be empty");
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+});
diff --git a/browser/base/content/test/urlbar/browser_action_searchengine.js b/browser/base/content/test/urlbar/browser_action_searchengine.js
new file mode 100644
index 000000000..d2115abba
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_searchengine.js
@@ -0,0 +1,36 @@
+add_task(function* () {
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ registerCleanupFunction(() => {
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+
+ try {
+ gBrowser.removeTab(tab);
+ } catch (ex) { /* tab may have already been closed in case of failure */ }
+
+ return PlacesTestUtils.clearHistory();
+ });
+
+ yield promiseAutocompleteResultPopup("open a search");
+ let result = gURLBar.popup.richlistbox.firstChild;
+
+ isnot(result, null, "Should have a result");
+ is(result.getAttribute("url"),
+ `moz-action:searchengine,{"engineName":"MozSearch","input":"open%20a%20search","searchQuery":"open%20a%20search"}`,
+ "Result should be a moz-action: for the correct search engine");
+ is(result.hasAttribute("image"), false, "Result shouldn't have an image attribute");
+
+ let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ result.click();
+ yield tabPromise;
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/?q=open+a+search", "Correct URL should be loaded");
+});
diff --git a/browser/base/content/test/urlbar/browser_action_searchengine_alias.js b/browser/base/content/test/urlbar/browser_action_searchengine_alias.js
new file mode 100644
index 000000000..1967d178a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_searchengine_alias.js
@@ -0,0 +1,35 @@
+add_task(function* () {
+ let iconURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC";
+ Services.search.addEngineWithDetails("MozSearch", iconURI, "moz", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ registerCleanupFunction(() => {
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+
+ try {
+ gBrowser.removeTab(tab);
+ } catch (ex) { /* tab may have already been closed in case of failure */ }
+
+ return PlacesTestUtils.clearHistory();
+ });
+
+ yield promiseAutocompleteResultPopup("moz open a search");
+
+ let result = gURLBar.popup.richlistbox.children[0];
+ ok(result.hasAttribute("image"), "Result should have an image attribute");
+ ok(result.getAttribute("image") === engine.iconURI.spec,
+ "Image attribute should have the search engine's icon");
+
+ let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ yield tabPromise;
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/?q=open+a+search");
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
new file mode 100644
index 000000000..a27f9672e
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+add_task(function* switchToTab() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:about");
+
+ yield promiseAutocompleteResultPopup("% about");
+
+ ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results");
+ let result = gURLBar.popup.richlistbox.children[1];
+ is(result.getAttribute("type"), "switchtab", "Expect right type attribute");
+ is(result.label, "about:about about:about Tab", "Result a11y label should be: <title> <url> Tab");
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* searchSuggestions() {
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ registerCleanupFunction(function () {
+ Services.search.currentEngine = oldCurrentEngine;
+ Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ });
+
+ yield promiseAutocompleteResultPopup("foo");
+ // Don't assume that the search doesn't match history or bookmarks left around
+ // by earlier tests.
+ Assert.ok(gURLBar.popup.richlistbox.children.length >= 3,
+ "Should get at least heuristic result + two search suggestions");
+ // The first expected search is the search term itself since the heuristic
+ // result will come before the search suggestions.
+ let expectedSearches = [
+ "foo",
+ "foofoo",
+ "foobar",
+ ];
+ for (let child of gURLBar.popup.richlistbox.children) {
+ if (child.getAttribute("type").split(/\s+/).indexOf("searchengine") >= 0) {
+ Assert.ok(expectedSearches.length > 0);
+ let suggestion = expectedSearches.shift();
+ Assert.equal(child.label, suggestion + " browser_searchSuggestionEngine searchSuggestionEngine.xml Search",
+ "Result label should be: <search term> <engine name> Search");
+ }
+ }
+ Assert.ok(expectedSearches.length == 0);
+ gURLBar.closePopup();
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js b/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js
new file mode 100644
index 000000000..e4e0daa8e
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js
@@ -0,0 +1,92 @@
+const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
+
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+function is_selected(index) {
+ is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ is(gURLBar.popup.oneOffSearchButtons.selectedButton, null,
+ "A result is selected, so the one-offs should not have a selection");
+}
+
+function is_selected_one_off(index) {
+ is(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, index,
+ "Expected one-off button should be selected");
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ is(gURLBar.popup.richlistbox.selectedIndex, -1,
+ "A one-off is selected, so the listbox should not have a selection");
+}
+
+add_task(function*() {
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
+ registerCleanupFunction(function* () {
+ yield PlacesTestUtils.clearHistory();
+ Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
+ });
+
+ let visits = [];
+ repeat(maxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ yield PlacesTestUtils.addVisits(visits);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ yield promiseAutocompleteResultPopup("example.com/autocomplete");
+
+ let popup = gURLBar.popup;
+ let results = popup.richlistbox.children;
+ is(results.length, maxResults,
+ "Should get maxResults=" + maxResults + " results");
+ is_selected(0);
+
+ info("Key Down to select the next item");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is_selected(1);
+
+ info("Key Down maxResults-1 times should select the first one-off");
+ repeat(maxResults - 1, () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected_one_off(0);
+
+ info("Key Down numButtons-1 should select the last one-off");
+ let numButtons =
+ gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
+ repeat(numButtons - 1, () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected_one_off(numButtons - 1);
+
+ info("Key Down twice more should select the second result");
+ repeat(2, () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected(1);
+
+ info("Key Down maxResults + numButtons times should wrap around");
+ repeat(maxResults + numButtons,
+ () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected(1);
+
+ info("Key Up maxResults + numButtons times should wrap around the other way");
+ repeat(maxResults + numButtons, () => EventUtils.synthesizeKey("VK_UP", {}));
+ is_selected(1);
+
+ info("Page Up will go up the list, but not wrap");
+ EventUtils.synthesizeKey("VK_PAGE_UP", {})
+ is_selected(0);
+
+ info("Page Up again will wrap around to the end of the list");
+ EventUtils.synthesizeKey("VK_PAGE_UP", {})
+ is_selected(maxResults - 1);
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_cursor.js b/browser/base/content/test/urlbar/browser_autocomplete_cursor.js
new file mode 100644
index 000000000..9cc2c6eac
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_cursor.js
@@ -0,0 +1,17 @@
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ yield promiseAutocompleteResultPopup("www.mozilla.org");
+
+ gURLBar.selectTextRange(4, 4);
+
+ is(gURLBar.popup.state, "open", "Popup should be open");
+ is(gURLBar.popup.richlistbox.selectedIndex, 0, "Should have selected something");
+
+ EventUtils.synthesizeKey("VK_RIGHT", {});
+ yield promisePopupHidden(gURLBar.popup);
+
+ is(gURLBar.selectionStart, 5, "Should have moved the cursor");
+ is(gURLBar.selectionEnd, 5, "And not selected anything");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js b/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js
new file mode 100644
index 000000000..19db1a368
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js
@@ -0,0 +1,48 @@
+add_task(function*() {
+ yield PlacesTestUtils.clearHistory();
+
+ yield PlacesTestUtils.addVisits([
+ { uri: makeURI("http://example.com/foo") },
+ { uri: makeURI("http://example.com/foo/bar") },
+ ]);
+
+ registerCleanupFunction(function* () {
+ yield PlacesTestUtils.clearHistory();
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ gURLBar.focus();
+
+ yield promiseAutocompleteResultPopup("http://example.com");
+
+ let popup = gURLBar.popup;
+ let list = popup.richlistbox;
+ let initialIndex = list.selectedIndex;
+
+ info("Key Down to select the next item.");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ let nextIndex = initialIndex + 1;
+ let nextValue = gURLBar.controller.getFinalCompleteValueAt(nextIndex);
+ is(list.selectedIndex, nextIndex, "The next item is selected.");
+ is(gURLBar.value, nextValue, "The selected URL is completed.");
+
+ info("Press backspace");
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ yield promiseSearchComplete();
+
+ let editedValue = gURLBar.textValue;
+ is(list.selectedIndex, initialIndex, "The initial index is selected again.");
+ isnot(editedValue, nextValue, "The URL has changed.");
+
+ let docLoad = waitForDocLoadAndStopIt("http://" + editedValue);
+
+ info("Press return to load edited URL.");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield Promise.all([
+ promisePopupHidden(gURLBar.popup),
+ docLoad,
+ ]);
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js b/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js
new file mode 100644
index 000000000..4e3c8943c
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js
@@ -0,0 +1,122 @@
+// The order of these tests matters!
+
+add_task(function* setup () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test" });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ yield BrowserTestUtils.removeTab(tab);
+ });
+ yield PlacesUtils.keywords.insert({ keyword: "keyword",
+ url: "http://example.com/?q=%s" });
+ // Needs at least one success.
+ ok(true, "Setup complete");
+});
+
+add_task(function* test_keyword() {
+ yield promiseAutocompleteResultPopup("keyword bear");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("d", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/?q=beard");
+});
+
+add_task(function* test_sametext() {
+ yield promiseAutocompleteResultPopup("example.com", window, true);
+
+ // Simulate re-entering the same text searched the last time. This may happen
+ // through a copy paste, but clipboard handling is not much reliable, so just
+ // fire an input event.
+ info("synthesize input event");
+ let event = document.createEvent("Events");
+ event.initEvent("input", true, true);
+ gURLBar.dispatchEvent(event);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/");
+});
+
+add_task(function* test_after_empty_search() {
+ yield promiseAutocompleteResultPopup("");
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/");
+});
+
+add_task(function* test_disabled_ac() {
+ // Disable autocomplete.
+ let suggestHistory = Preferences.get("browser.urlbar.suggest.history");
+ Preferences.set("browser.urlbar.suggest.history", false);
+ let suggestBookmarks = Preferences.get("browser.urlbar.suggest.bookmark");
+ Preferences.set("browser.urlbar.suggest.bookmark", false);
+ let suggestOpenPages = Preferences.get("browser.urlbar.suggest.openpage");
+ Preferences.set("browser.urlbar.suggest.openpages", false);
+
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ function* cleanup() {
+ Preferences.set("browser.urlbar.suggest.history", suggestHistory);
+ Preferences.set("browser.urlbar.suggest.bookmark", suggestBookmarks);
+ Preferences.set("browser.urlbar.suggest.openpage", suggestOpenPages);
+
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ if (engine) {
+ Services.search.removeEngine(engine);
+ }
+ }
+ registerCleanupFunction(cleanup);
+
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/?q=ex");
+ yield cleanup();
+});
+
+add_task(function* test_delay() {
+ const TIMEOUT = 10000;
+ // Set a large delay.
+ let delay = Preferences.get("browser.urlbar.delay");
+ Preferences.set("browser.urlbar.delay", TIMEOUT);
+
+ registerCleanupFunction(function* () {
+ Preferences.set("browser.urlbar.delay", delay);
+ });
+
+ // This is needed to clear the current value, otherwise autocomplete may think
+ // the user removed text from the end.
+ let start = Date.now();
+ yield promiseAutocompleteResultPopup("");
+ Assert.ok((Date.now() - start) < TIMEOUT);
+
+ start = Date.now();
+ gURLBar.closePopup();
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/");
+ Assert.ok((Date.now() - start) < TIMEOUT);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_no_title.js b/browser/base/content/test/urlbar/browser_autocomplete_no_title.js
new file mode 100644
index 000000000..8d608550b
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_no_title.js
@@ -0,0 +1,15 @@
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ let uri = NetUtil.newURI("http://bug1060642.example.com/beards/are/pretty/great");
+ yield PlacesTestUtils.addVisits([{uri: uri, title: ""}]);
+
+ yield promiseAutocompleteResultPopup("bug1060642");
+ ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results");
+ let result = gURLBar.popup.richlistbox.children[1];
+ is(result._titleText.textContent, "bug1060642.example.com", "Result title should be as expected");
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js b/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js
new file mode 100644
index 000000000..8a69b4b44
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js
@@ -0,0 +1,102 @@
+add_task(function*() {
+ registerCleanupFunction(() => {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ });
+
+ function* addTagItem(tagName) {
+ let uri = NetUtil.newURI(`http://example.com/this/is/tagged/${tagName}`);
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ `test ${tagName}`);
+ PlacesUtils.tagging.tagURI(uri, [tagName]);
+ yield PlacesTestUtils.addVisits([{uri: uri, title: `Test page with tag ${tagName}`}]);
+ }
+
+ // We use different tags for each part of the test, as otherwise the
+ // autocomplete code tries to be smart by using the previously cached element
+ // without updating it (since all parameters it knows about are the same).
+
+ let testcases = [{
+ description: "Test with suggest.bookmark=true",
+ tagName: "tagtest1",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "tagtest1",
+ expected: {
+ type: "bookmark",
+ typeImageVisible: true,
+ },
+ }, {
+ description: "Test with suggest.bookmark=false",
+ tagName: "tagtest2",
+ prefs: {
+ "suggest.bookmark": false,
+ },
+ input: "tagtest2",
+ expected: {
+ type: "tag",
+ typeImageVisible: false,
+ },
+ }, {
+ description: "Test with suggest.bookmark=true (again)",
+ tagName: "tagtest3",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "tagtest3",
+ expected: {
+ type: "bookmark",
+ typeImageVisible: true,
+ },
+ }, {
+ description: "Test with bookmark restriction token",
+ tagName: "tagtest4",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "* tagtest4",
+ expected: {
+ type: "bookmark",
+ typeImageVisible: true,
+ },
+ }, {
+ description: "Test with history restriction token",
+ tagName: "tagtest5",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "^ tagtest5",
+ expected: {
+ type: "tag",
+ typeImageVisible: false,
+ },
+ }];
+
+ for (let testcase of testcases) {
+ info(`Test case: ${testcase.description}`);
+
+ yield addTagItem(testcase.tagName);
+ for (let prefName of Object.keys(testcase.prefs)) {
+ Services.prefs.setBoolPref(`browser.urlbar.${prefName}`, testcase.prefs[prefName]);
+ }
+
+ yield promiseAutocompleteResultPopup(testcase.input);
+ let result = gURLBar.popup.richlistbox.children[1];
+ ok(result && !result.collasped, "Should have result");
+
+ is(result.getAttribute("type"), testcase.expected.type, "Result should have expected type");
+
+ let typeIconStyle = window.getComputedStyle(result._typeIcon);
+ let imageURL = typeIconStyle.listStyleImage;
+ if (testcase.expected.typeImageVisible) {
+ ok(/^url\(.+\)$/.test(imageURL), "Type image should be visible");
+ } else {
+ is(imageURL, "none", "Type image should be hidden");
+ }
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ }
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js
new file mode 100644
index 000000000..89f604491
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test_switchtab_override() {
+ let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+ info("Opening first tab");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
+
+ info("Opening and selecting second tab");
+ let secondTab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(() => {
+ try {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+ } catch (ex) { /* tabs may have already been closed in case of failure */ }
+ });
+
+ info("Wait for autocomplete")
+ let deferred = Promise.defer();
+ let onSearchComplete = gURLBar.onSearchComplete;
+ registerCleanupFunction(() => {
+ gURLBar.onSearchComplete = onSearchComplete;
+ });
+ gURLBar.onSearchComplete = function () {
+ ok(gURLBar.popupOpen, "The autocomplete popup is correctly open");
+ onSearchComplete.apply(gURLBar);
+ deferred.resolve();
+ }
+
+ gURLBar.focus();
+ gURLBar.value = "dummy_pag";
+ EventUtils.synthesizeKey("e", {});
+ yield deferred.promise;
+
+ info("Select second autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found");
+
+ info("Override switch-to-tab");
+ deferred = Promise.defer();
+ // In case of failure this would switch tab.
+ let onTabSelect = event => {
+ deferred.reject(new Error("Should have overridden switch to tab"));
+ };
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false);
+ registerCleanupFunction(() => {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ });
+ // Otherwise it would load the page.
+ BrowserTestUtils.browserLoaded(secondTab.linkedBrowser).then(deferred.resolve);
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ info(`gURLBar.value = ${gURLBar.value}`);
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+ yield deferred.promise;
+
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js
new file mode 100644
index 000000000..2d97ea07b
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test_switchtab_override_keynav() {
+ let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+ info("Opening first tab");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
+
+ info("Opening and selecting second tab");
+ let secondTab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(() => {
+ try {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+ } catch (ex) { /* tabs may have already been closed in case of failure */ }
+ return PlacesTestUtils.clearHistory();
+ });
+
+ gURLBar.focus();
+ gURLBar.value = "dummy_pag";
+ EventUtils.synthesizeKey("e", {});
+ yield promiseSearchComplete();
+
+ info("Select second autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found");
+
+ info("Shift+left on switch-to-tab entry");
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+ EventUtils.synthesizeKey("VK_LEFT", { shiftKey: true });
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+
+ ok(!/moz-action:switchtab/.test(gURLBar.inputField.value), "switch to tab should be hidden");
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js b/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
new file mode 100644
index 000000000..9e779ade1
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test_ignoreFragment() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home#1");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ let numTabsAtStart = gBrowser.tabs.length;
+
+ switchTab("about:home#1", true);
+ switchTab("about:mozilla", true);
+
+ let hashChangePromise = ContentTask.spawn(tabRefAboutHome.linkedBrowser, null, function* () {
+ yield ContentTaskUtils.waitForEvent(this, "hashchange", false);
+ });
+ switchTab("about:home#2", true, { ignoreFragment: "whenComparingAndReplace" });
+ is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to");
+ yield hashChangePromise;
+ is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref");
+ switchTab("about:mozilla", true);
+ switchTab("about:home#3", true, { ignoreFragment: "whenComparing" });
+ is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to");
+ is(gBrowser.currentURI.ref, "2", "The ref should be unchanged since the fragment is only ignored when comparing");
+ switchTab("about:mozilla", true);
+ switchTab("about:home#1", false);
+ isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab");
+ is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened");
+ switchTab("about:mozilla", true);
+ switchTab("about:home", true, {ignoreFragment: "whenComparingAndReplace"});
+ yield BrowserTestUtils.waitForCondition(function() {
+ return tabRefAboutHome.linkedBrowser.currentURI.spec == "about:home";
+ });
+ is(tabRefAboutHome.linkedBrowser.currentURI.spec, "about:home", "about:home shouldn't have hash");
+ switchTab("about:about", false, { ignoreFragment: "whenComparingAndReplace" });
+ cleanupTestTabs();
+});
+
+add_task(function* test_ignoreQueryString() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ switchTab("about:home?hello=firefox", true);
+ switchTab("about:home?hello=firefoxos", false);
+ // Remove the last opened tab to test ignoreQueryString option.
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos", true, { ignoreQueryString: true });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ is(gBrowser.currentURI.spec, "about:home?hello=firefox", "The spec should NOT be updated to the new query string");
+ cleanupTestTabs();
+});
+
+add_task(function* test_replaceQueryString() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ switchTab("about:home", false);
+ switchTab("about:home?hello=firefox", true);
+ switchTab("about:home?hello=firefoxos", false);
+ // Remove the last opened tab to test replaceQueryString option.
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos", true, { replaceQueryString: true });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ // Wait for the tab to load the new URI spec.
+ yield BrowserTestUtils.browserLoaded(tabRefAboutHome.linkedBrowser);
+ is(gBrowser.currentURI.spec, "about:home?hello=firefoxos", "The spec should be updated to the new spec");
+ cleanupTestTabs();
+});
+
+add_task(function* test_replaceQueryStringAndFragment() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox#aaa");
+ let tabRefAboutMozilla =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla?hello=firefoxos#aaa");
+
+ switchTab("about:home", false);
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefox#aaa", true);
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ switchTab("about:mozilla?hello=firefox#bbb", true, { replaceQueryString: true, ignoreFragment: "whenComparingAndReplace" });
+ is(tabRefAboutMozilla, gBrowser.selectedTab, "Selected tab should be the initial about:mozilla tab");
+ switchTab("about:home?hello=firefoxos#bbb", true, { ignoreQueryString: true, ignoreFragment: "whenComparingAndReplace" });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ cleanupTestTabs();
+});
+
+add_task(function* test_ignoreQueryStringIgnoresFragment() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox#aaa");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla?hello=firefoxos#aaa");
+
+ switchTab("about:home?hello=firefox#bbb", false, { ignoreQueryString: true });
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos#aaa", true, { ignoreQueryString: true });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ cleanupTestTabs();
+});
+
+// Begin helpers
+
+function cleanupTestTabs() {
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+}
+
+function switchTab(aURI, aShouldFindExistingTab, aOpenParams = {}) {
+ // Build the description before switchToTabHavingURI deletes the object properties.
+ let msg = `Should switch to existing ${aURI} tab if one existed, ` +
+ `${(aOpenParams.ignoreFragment ? "ignoring" : "including")} fragment portion, `;
+ if (aOpenParams.replaceQueryString) {
+ msg += "replacing";
+ } else if (aOpenParams.ignoreQueryString) {
+ msg += "ignoring";
+ } else {
+ msg += "including";
+ }
+ msg += " query string.";
+ let tabFound = switchToTabHavingURI(aURI, true, aOpenParams);
+ is(tabFound, aShouldFindExistingTab, msg);
+}
+
+registerCleanupFunction(cleanupTestTabs);
diff --git a/browser/base/content/test/urlbar/browser_bug1070778.js b/browser/base/content/test/urlbar/browser_bug1070778.js
new file mode 100644
index 000000000..ab88d04d8
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1070778.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function is_selected(index) {
+ is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
+}
+
+add_task(function*() {
+ let bookmarks = [];
+ bookmarks.push((yield PlacesUtils.bookmarks
+ .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test" })));
+ yield PlacesUtils.keywords.insert({ keyword: "keyword",
+ url: "http://example.com/?q=%s" });
+
+ // This item only needed so we can select the keyword item, select something
+ // else, then select the keyword item again.
+ bookmarks.push((yield PlacesUtils.bookmarks
+ .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/keyword",
+ title: "keyword abc" })));
+
+ registerCleanupFunction(function* () {
+ for (let bm of bookmarks) {
+ yield PlacesUtils.bookmarks.remove(bm);
+ }
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ yield promiseAutocompleteResultPopup("keyword a");
+
+ // First item should already be selected
+ is_selected(0);
+ // Select next one (important!)
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is_selected(1);
+ // Re-select keyword item
+ EventUtils.synthesizeKey("VK_UP", {});
+ is_selected(0);
+
+ EventUtils.synthesizeKey("b", {});
+ yield promiseSearchComplete();
+
+ is(gURLBar.textValue, "keyword ab", "urlbar should have expected input");
+
+ let result = gURLBar.popup.richlistbox.firstChild;
+ isnot(result, null, "Should have first item");
+ let uri = NetUtil.newURI(result.getAttribute("url"));
+ is(uri.spec, PlacesUtils.mozActionURI("keyword", {url: "http://example.com/?q=ab", input: "keyword ab"}), "Expect correct url");
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js
new file mode 100644
index 000000000..d165d7304
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js
@@ -0,0 +1,29 @@
+add_task(function* test_switchtab_decodeuri() {
+ info("Opening first tab");
+ const TEST_URL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#test%7C1";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Opening and selecting second tab");
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ info("Wait for autocomplete")
+ yield promiseAutocompleteResultPopup("dummy_page");
+
+ info("Select autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(gURLBar.value.startsWith("moz-action:switchtab"), "switch to tab entry found");
+
+ info("switch-to-tab");
+ yield new Promise((resolve, reject) => {
+ // In case of success it should switch tab.
+ gBrowser.tabContainer.addEventListener("TabSelect", function select() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", select, false);
+ is(gBrowser.selectedTab, tab, "Should have switched to the right tab");
+ resolve();
+ }, false);
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ });
+
+ gBrowser.removeCurrentTab();
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js
new file mode 100644
index 000000000..3b4a44e76
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js
@@ -0,0 +1,16 @@
+add_task(function* test_remotetab_opens() {
+ const url = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+ yield BrowserTestUtils.withNewTab({url: "about:robots", gBrowser}, function* () {
+ // Set the urlbar to include the moz-action
+ gURLBar.value = "moz-action:remotetab," + JSON.stringify({ url });
+ // Focus the urlbar so we can press enter
+ gURLBar.focus();
+
+ // The URL is going to open in the current tab as it is currently about:blank
+ let promiseTabLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield promiseTabLoaded;
+
+ Assert.equal(gBrowser.selectedTab.linkedBrowser.currentURI.spec, url, "correct URL loaded");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_bug304198.js b/browser/base/content/test/urlbar/browser_bug304198.js
new file mode 100644
index 000000000..dc8d39fae
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug304198.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* () {
+ let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL;
+
+ charsToDelete = 5;
+ deletedURLTab = gBrowser.addTab();
+ fullURLTab = gBrowser.addTab();
+ partialURLTab = gBrowser.addTab();
+ testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+ let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL);
+ let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL);
+ let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL);
+ deletedURLTab.linkedBrowser.loadURI(testURL);
+ fullURLTab.linkedBrowser.loadURI(testURL);
+ partialURLTab.linkedBrowser.loadURI(testURL);
+ yield Promise.all([loaded1, loaded2, loaded3]);
+
+ testURL = gURLBar.trimValue(testURL);
+ testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
+
+ function cleanUp() {
+ gBrowser.removeTab(fullURLTab);
+ gBrowser.removeTab(partialURLTab);
+ gBrowser.removeTab(deletedURLTab);
+ }
+
+ function* cycleTabs() {
+ yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
+
+ yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
+ is(gURLBar.textValue, testPartialURL, 'gURLBar.textValue should be testPartialURL after switching back to partialURLTab');
+ yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
+ is(gURLBar.textValue, '', 'gURLBar.textValue should be "" after switching back to deletedURLTab');
+
+ yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
+ }
+
+ function urlbarBackspace() {
+ return new Promise((resolve, reject) => {
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener("input", function () {
+ gURLBar.removeEventListener("input", arguments.callee, false);
+ resolve();
+ }, false);
+ gURLBar.focus();
+ if (gURLBar.selectionStart == gURLBar.selectionEnd) {
+ gURLBar.selectionStart = gURLBar.selectionEnd = gURLBar.textValue.length;
+ }
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ });
+ }
+
+ function* prepareDeletedURLTab() {
+ yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to deletedURLTab');
+
+ // simulate the user removing the whole url from the location bar
+ gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", true);
+
+ yield urlbarBackspace();
+ is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)');
+ if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
+ gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+ }
+ }
+
+ function* prepareFullURLTab() {
+ yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to fullURLTab');
+ }
+
+ function* preparePartialURLTab() {
+ yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to partialURLTab');
+
+ // simulate the user removing part of the url from the location bar
+ gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", false);
+
+ let deleted = 0;
+ while (deleted < charsToDelete) {
+ yield urlbarBackspace(arguments.callee);
+ deleted++;
+ }
+
+ is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)");
+ if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
+ gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+ }
+ }
+
+ // prepare the three tabs required by this test
+
+ // First tab
+ yield* prepareFullURLTab();
+ yield* preparePartialURLTab();
+ yield* prepareDeletedURLTab();
+
+ // now cycle the tabs and make sure everything looks good
+ yield* cycleTabs();
+ cleanUp();
+});
+
+
diff --git a/browser/base/content/test/urlbar/browser_bug556061.js b/browser/base/content/test/urlbar/browser_bug556061.js
new file mode 100644
index 000000000..4c6ac5bf5
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug556061.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+var testActionURL = "moz-action:switchtab," + JSON.stringify({url: testURL});
+testURL = gURLBar.trimValue(testURL);
+var testTab;
+
+function runNextTest() {
+ if (tests.length) {
+ let t = tests.shift();
+ waitForClipboard(t.expected, t.setup, function() {
+ t.success();
+ runNextTest();
+ }, cleanup);
+ }
+ else {
+ cleanup();
+ }
+}
+
+function cleanup() {
+ gBrowser.removeTab(testTab);
+ finish();
+}
+
+var tests = [
+ {
+ expected: testURL,
+ setup: function() {
+ gURLBar.value = testActionURL;
+ gURLBar.valueIsTyped = true;
+ is(gURLBar.value, testActionURL, "gURLBar starts with the correct real value");
+ is(gURLBar.textValue, testURL, "gURLBar starts with the correct display value");
+
+ // Focus the urlbar so we can select it all & copy
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ },
+ success: function() {
+ is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying");
+ }
+ },
+ {
+ expected: testURL.substring(0, 10),
+ setup: function() {
+ // Set selectionStart/End manually and make sure it matches the substring
+ gURLBar.selectionStart = 0;
+ gURLBar.selectionEnd = 10;
+ goDoCommand("cmd_copy");
+ },
+ success: function() {
+ is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying");
+ }
+ },
+ {
+ expected: testURL,
+ setup: function() {
+ // Setup for cut test...
+ // Select all
+ gURLBar.select();
+ goDoCommand("cmd_cut");
+ },
+ success: function() {
+ is(gURLBar.value, "", "gURLBar.value is now empty");
+ }
+ },
+ {
+ expected: testURL.substring(testURL.length - 10, testURL.length),
+ setup: function() {
+ // Reset urlbar value
+ gURLBar.value = testActionURL;
+ gURLBar.valueIsTyped = true;
+ // Sanity check that we have the right value
+ is(gURLBar.value, testActionURL, "gURLBar starts with the correct real value");
+ is(gURLBar.textValue, testURL, "gURLBar starts with the correct display value");
+
+ // Now just select part of the value & cut that.
+ gURLBar.selectionStart = testURL.length - 10;
+ gURLBar.selectionEnd = testURL.length;
+ goDoCommand("cmd_cut");
+ },
+ success: function() {
+ is(gURLBar.value, testURL.substring(0, testURL.length - 10), "gURLBar.value has the correct value");
+ }
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+ testTab = gBrowser.addTab();
+ gBrowser.selectedTab = testTab;
+
+ // Kick off the testing
+ runNextTest();
+}
diff --git a/browser/base/content/test/urlbar/browser_bug562649.js b/browser/base/content/test/urlbar/browser_bug562649.js
new file mode 100644
index 000000000..f56e430ee
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug562649.js
@@ -0,0 +1,24 @@
+function test() {
+ const URI = "data:text/plain,bug562649";
+ browserDOMWindow.openURI(makeURI(URI),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI");
+ is(gURLBar.value, URI, "location bar value matches test URI");
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs");
+ is(gURLBar.value, URI, "location bar value matches test URI after switching tabs");
+
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ is(gBrowser.userTypedValue, null, "userTypedValue is null as the page has loaded");
+ is(gURLBar.value, URI, "location bar value matches test URI as the page has loaded");
+
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+ finish();
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_bug623155.js b/browser/base/content/test/urlbar/browser_bug623155.js
new file mode 100644
index 000000000..dd6ff8c85
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug623155.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const REDIRECT_FROM = "https://example.com/browser/browser/base/content/test/urlbar/" +
+ "redirect_bug623155.sjs";
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function isRedirectedURISpec(aURISpec) {
+ return isRedirectedURI(Services.io.newURI(aURISpec, null, null));
+}
+
+function isRedirectedURI(aURI) {
+ // Compare only their before-hash portion.
+ return Services.io.newURI(REDIRECT_TO, null, null)
+ .equalsExceptRef(aURI);
+}
+
+/*
+ Test.
+
+1. Load
+https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#BG
+ in a background tab.
+
+2. The redirected URI is <https://www.bank1.com/#BG>, which displayes a cert
+ error page.
+
+3. Switch the tab to foreground.
+
+4. Check the URLbar's value, expecting <https://www.bank1.com/#BG>
+
+5. Load
+https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#FG
+ in the foreground tab.
+
+6. The redirected URI is <https://www.bank1.com/#FG>. And this is also
+ a cert-error page.
+
+7. Check the URLbar's value, expecting <https://www.bank1.com/#FG>
+
+8. End.
+
+ */
+
+var gNewTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Load a URI in the background.
+ gNewTab = gBrowser.addTab(REDIRECT_FROM + "#BG");
+ gBrowser.getBrowserForTab(gNewTab)
+ .webProgress
+ .addProgressListener(gWebProgressListener,
+ Components.interfaces.nsIWebProgress
+ .NOTIFY_LOCATION);
+}
+
+var gWebProgressListener = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ // ---------------------------------------------------------------------------
+ // NOTIFY_LOCATION mode should work fine without these methods.
+ //
+ // onStateChange: function() {},
+ // onStatusChange: function() {},
+ // onProgressChange: function() {},
+ // onSecurityChange: function() {},
+ // ----------------------------------------------------------------------------
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (!aRequest) {
+ // This is bug 673752, or maybe initial "about:blank".
+ return;
+ }
+
+ ok(gNewTab, "There is a new tab.");
+ ok(isRedirectedURI(aLocation),
+ "onLocationChange catches only redirected URI.");
+
+ if (aLocation.ref == "BG") {
+ // This is background tab's request.
+ isnot(gNewTab, gBrowser.selectedTab, "This is a background tab.");
+ } else if (aLocation.ref == "FG") {
+ // This is foreground tab's request.
+ is(gNewTab, gBrowser.selectedTab, "This is a foreground tab.");
+ }
+ else {
+ // We shonuld not reach here.
+ ok(false, "This URI hash is not expected:" + aLocation.ref);
+ }
+
+ let isSelectedTab = gNewTab.selected;
+ setTimeout(delayed, 0, isSelectedTab);
+ }
+};
+
+function delayed(aIsSelectedTab) {
+ // Switch tab and confirm URL bar.
+ if (!aIsSelectedTab) {
+ gBrowser.selectedTab = gNewTab;
+ }
+
+ let currentURI = gBrowser.selectedBrowser.currentURI.spec;
+ ok(isRedirectedURISpec(currentURI),
+ "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab);
+ is(gURLBar.value, currentURI,
+ "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab);
+
+ if (!aIsSelectedTab) {
+ // If this was a background request, go on a foreground request.
+ gBrowser.selectedBrowser.loadURI(REDIRECT_FROM + "#FG");
+ }
+ else {
+ // Othrewise, nothing to do remains.
+ finish();
+ }
+}
+
+/* Cleanup */
+registerCleanupFunction(function() {
+ if (gNewTab) {
+ gBrowser.getBrowserForTab(gNewTab)
+ .webProgress
+ .removeProgressListener(gWebProgressListener);
+
+ gBrowser.removeTab(gNewTab);
+ }
+ gNewTab = null;
+});
diff --git a/browser/base/content/test/urlbar/browser_bug783614.js b/browser/base/content/test/urlbar/browser_bug783614.js
new file mode 100644
index 000000000..ebc62e8fa
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug783614.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ gURLBar.focus();
+ gURLBar.inputField.value = "https://example.com/";
+ gURLBar.selectionStart = 4;
+ gURLBar.selectionEnd = 5;
+ goDoCommand("cmd_cut");
+ is(gURLBar.inputField.value, "http://example.com/", "location bar value after cutting 's' from https");
+ gURLBar.handleRevert();
+}
diff --git a/browser/base/content/test/urlbar/browser_canonizeURL.js b/browser/base/content/test/urlbar/browser_canonizeURL.js
new file mode 100644
index 000000000..59ab54ca0
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_canonizeURL.js
@@ -0,0 +1,42 @@
+add_task(function*() {
+ let testcases = [
+ ["example", "http://www.example.net/", { shiftKey: true }],
+ // Check that a direct load is not overwritten by a previous canonization.
+ ["http://example.com/test/", "http://example.com/test/", {}],
+ ["ex-ample", "http://www.ex-ample.net/", { shiftKey: true }],
+ [" example ", "http://www.example.net/", { shiftKey: true }],
+ [" example/foo ", "http://www.example.net/foo", { shiftKey: true }],
+ [" example/foo bar ", "http://www.example.net/foo%20bar", { shiftKey: true }],
+ ["example.net", "http://example.net/", { shiftKey: true }],
+ ["http://example", "http://example/", { shiftKey: true }],
+ ["example:8080", "http://example:8080/", { shiftKey: true }],
+ ["ex-ample.foo", "http://ex-ample.foo/", { shiftKey: true }],
+ ["example.foo/bar ", "http://example.foo/bar", { shiftKey: true }],
+ ["1.1.1.1", "http://1.1.1.1/", { shiftKey: true }],
+ ["ftp://example", "ftp://example/", { shiftKey: true }],
+ ["ftp.example.bar", "http://ftp.example.bar/", { shiftKey: true }],
+ ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec, { shiftKey: true }],
+ ];
+
+ // Disable autoFill for this test, since it could mess up the results.
+ let autoFill = Preferences.get("browser.urlbar.autoFill");
+ Preferences.set("browser.urlbar.autoFill", false);
+ registerCleanupFunction(() => {
+ Preferences.set("browser.urlbar.autoFill", autoFill);
+ });
+
+ for (let [inputValue, expectedURL, options] of testcases) {
+ let promiseLoad = waitForDocLoadAndStopIt(expectedURL);
+ gURLBar.focus();
+ if (Object.keys(options).length > 0) {
+ gURLBar.selectionStart = gURLBar.selectionEnd =
+ gURLBar.inputField.value.length;
+ gURLBar.inputField.value = inputValue.slice(0, -1);
+ EventUtils.synthesizeKey(inputValue.slice(-1), {});
+ } else {
+ gURLBar.textValue = inputValue;
+ }
+ EventUtils.synthesizeKey("VK_RETURN", options);
+ yield promiseLoad;
+ }
+});
diff --git a/browser/base/content/test/urlbar/browser_dragdropURL.js b/browser/base/content/test/urlbar/browser_dragdropURL.js
new file mode 100644
index 000000000..ec2906700
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_dragdropURL.js
@@ -0,0 +1,15 @@
+"use strict";
+
+const TEST_URL = "data:text/html,a test page";
+const DRAG_URL = "http://www.example.com/";
+
+add_task(function* checkURLBarUpdateForDrag() {
+ yield BrowserTestUtils.withNewTab(TEST_URL, function* (browser) {
+ // Have to use something other than the URL bar as a source, so picking the
+ // downloads button somewhat arbitrarily:
+ EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+ [[{type: "text/plain", data: DRAG_URL}]], "copy", window);
+ is(gURLBar.value, TEST_URL, "URL bar value should not have changed");
+ is(gBrowser.selectedBrowser.userTypedValue, null, "Stored URL bar value should not have changed");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_locationBarCommand.js b/browser/base/content/test/urlbar/browser_locationBarCommand.js
new file mode 100644
index 000000000..935bdf758
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_locationBarCommand.js
@@ -0,0 +1,218 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_VALUE = "example.com";
+const START_VALUE = "example.org";
+
+add_task(function* setup() {
+ Services.prefs.setBoolPref("browser.altClickSave", true);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.altClickSave");
+ });
+});
+
+add_task(function* alt_left_click_test() {
+ info("Running test: Alt left click");
+
+ // Monkey patch saveURL() to avoid dealing with file save code paths.
+ let oldSaveURL = saveURL;
+ let saveURLPromise = new Promise(resolve => {
+ saveURL = () => {
+ // Restore old saveURL() value.
+ saveURL = oldSaveURL;
+ resolve();
+ };
+ });
+
+ triggerCommand(true, {altKey: true});
+
+ yield saveURLPromise;
+ ok(true, "SaveURL was called");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+});
+
+add_task(function* shift_left_click_test() {
+ info("Running test: Shift left click");
+
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ triggerCommand(true, {shiftKey: true});
+ let win = yield newWindowPromise;
+
+ // Wait for the initial browser to load.
+ let browser = win.gBrowser.selectedBrowser;
+ let destinationURL = "http://" + TEST_VALUE + "/";
+ yield Promise.all([
+ BrowserTestUtils.browserLoaded(browser),
+ BrowserTestUtils.waitForLocationChange(win.gBrowser, destinationURL)
+ ]);
+
+ info("URL should be loaded in a new window");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
+ is(win.gURLBar.textValue, TEST_VALUE, "New URL is loaded in new window");
+
+ // Cleanup.
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+add_task(function* right_click_test() {
+ info("Running test: Right click on go button");
+
+ // Add a new tab.
+ yield* promiseOpenNewTab();
+
+ triggerCommand(true, {button: 2});
+
+ // Right click should do nothing (context menu will be shown).
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* shift_accel_left_click_test() {
+ info("Running test: Shift+Ctrl/Cmd left click on go button");
+
+ // Add a new tab.
+ let tab = yield* promiseOpenNewTab();
+
+ let loadStartedPromise = promiseLoadStarted();
+ triggerCommand(true, {accelKey: true, shiftKey: true});
+ yield loadStartedPromise;
+
+ // Check the load occurred in a new background tab.
+ info("URL should be loaded in a new background tab");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ ok(!gURLBar.focused, "Urlbar is no longer focused after urlbar command");
+ is(gBrowser.selectedTab, tab, "Focus did not change to the new tab");
+
+ // Select the new background tab
+ gBrowser.selectedTab = gBrowser.selectedTab.nextSibling;
+ is(gURLBar.value, TEST_VALUE, "New URL is loaded in new tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* load_in_current_tab_test() {
+ let tests = [
+ {desc: "Simple return keypress"},
+ {desc: "Left click on go button", click: true},
+ {desc: "Ctrl/Cmd+Return keypress", event: {accelKey: true}},
+ {desc: "Alt+Return keypress in a blank tab", event: {altKey: true}}
+ ];
+
+ for (let test of tests) {
+ info(`Running test: ${test.desc}`);
+
+ // Add a new tab.
+ let tab = yield* promiseOpenNewTab();
+
+ // Trigger a load and check it occurs in the current tab.
+ let loadStartedPromise = promiseLoadStarted();
+ triggerCommand(test.click || false, test.event || {});
+ yield loadStartedPromise;
+
+ info("URL should be loaded in the current tab");
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ }
+});
+
+add_task(function* load_in_new_tab_test() {
+ let tests = [
+ {desc: "Ctrl/Cmd left click on go button", click: true, event: {accelKey: true}},
+ {desc: "Alt+Return keypress in a dirty tab", event: {altKey: true}, url: START_VALUE}
+ ];
+
+ for (let test of tests) {
+ info(`Running test: ${test.desc}`);
+
+ // Add a new tab.
+ let tab = yield* promiseOpenNewTab(test.url || "about:blank");
+
+ // Trigger a load and check it occurs in the current tab.
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ triggerCommand(test.click || false, test.event || {});
+ yield tabSwitchedPromise;
+
+ // Check the load occurred in a new tab.
+ info("URL should be loaded in a new focused tab");
+ is(gURLBar.inputField.value, TEST_VALUE, "Urlbar still has the value we entered");
+ yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function triggerCommand(shouldClick, event) {
+ gURLBar.value = TEST_VALUE;
+ gURLBar.focus();
+
+ if (shouldClick) {
+ is(gURLBar.getAttribute("pageproxystate"), "invalid",
+ "page proxy state must be invalid for go button to be visible");
+
+ let goButton = document.getElementById("urlbar-go-button");
+ EventUtils.synthesizeMouseAtCenter(goButton, event);
+ } else {
+ EventUtils.synthesizeKey("VK_RETURN", event);
+ }
+}
+
+function promiseLoadStarted() {
+ return new Promise(resolve => {
+ gBrowser.addTabsProgressListener({
+ onStateChange(browser, webProgress, req, flags, status) {
+ if (flags & Ci.nsIWebProgressListener.STATE_START) {
+ gBrowser.removeTabsProgressListener(this);
+ resolve();
+ }
+ }
+ });
+ });
+}
+
+function* promiseOpenNewTab(url = "about:blank") {
+ let tab = gBrowser.addTab(url);
+ let tabSwitchPromise = promiseNewTabSwitched(tab);
+ gBrowser.selectedTab = tab;
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ yield tabSwitchPromise;
+ return tab;
+}
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+ executeSoon(resolve);
+ });
+ });
+}
+
+function promiseCheckChildNoFocusedElement(browser)
+{
+ if (!gMultiProcessBrowser) {
+ Assert.equal(Services.focus.focusedElement, null, "There should be no focused element");
+ return null;
+ }
+
+ return ContentTask.spawn(browser, { }, function* () {
+ const fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ Assert.equal(fm.focusedElement, null, "There should be no focused element");
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
new file mode 100644
index 000000000..31fc84768
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const url = "data:text/html,<body>hi";
+
+add_task(function*() {
+ yield* testURL(url, urlEnter);
+ yield* testURL(url, urlClick);
+});
+
+function urlEnter(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
+
+function urlClick(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ let goButton = document.getElementById("urlbar-go-button");
+ EventUtils.synthesizeMouseAtCenter(goButton, {});
+}
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+ executeSoon(resolve);
+ });
+ });
+}
+
+function* testURL(url, loadFunc, endFunc) {
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ let browser = gBrowser.selectedBrowser;
+
+ let pageshowPromise = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+
+ yield tabSwitchedPromise;
+ yield pageshowPromise;
+
+ let pagePrincipal = gBrowser.contentPrincipal;
+ loadFunc(url);
+
+ yield BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+
+ yield ContentTask.spawn(browser, { isRemote: gMultiProcessBrowser },
+ function* (arg) {
+ const fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ Assert.equal(fm.focusedElement, null, "focusedElement not null");
+
+ if (arg.isRemote) {
+ Assert.equal(fm.activeWindow, content, "activeWindow not correct");
+ }
+ });
+
+ is(document.activeElement, browser, "content window should be focused");
+
+ ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
+ "load of " + url + " by " + loadFunc.name + " should produce a page with a different principal");
+
+ gBrowser.removeTab(tab);
+}
diff --git a/browser/base/content/test/urlbar/browser_moz_action_link.js b/browser/base/content/test/urlbar/browser_moz_action_link.js
new file mode 100644
index 000000000..ed2d36ee5
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_moz_action_link.js
@@ -0,0 +1,31 @@
+"use strict";
+
+const kURIs = [
+ "moz-action:foo,",
+ "moz-action:foo",
+];
+
+add_task(function*() {
+ for (let uri of kURIs) {
+ let dataURI = `data:text/html,<a id=a href="${uri}" target=_blank>Link</a>`;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI);
+
+ let tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, function() {});
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ content.document.getElementById("a").click();
+ });
+ yield tabSwitchPromise;
+ isnot(gBrowser.selectedTab, tab, "Switched to new tab!");
+ is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank");
+ let newTab = gBrowser.selectedTab;
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gBrowser.selectedTab, newTab, "Switched to new tab again!");
+ is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank after tab switch");
+ // Finally, check that directly setting it produces the right results, too:
+ URLBarSetURI(makeURI(uri));
+ is(gURLBar.value, "about:blank", "URL bar should still be displaying about:blank");
+ yield BrowserTestUtils.removeTab(newTab);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
new file mode 100644
index 000000000..e9ba8d989
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
@@ -0,0 +1,49 @@
+function test() {
+ waitForExplicitFinish();
+ testNext();
+}
+
+var pairs = [
+ ["javascript:", ""],
+ ["javascript:1+1", "1+1"],
+ ["javascript:document.domain", "document.domain"],
+ ["data:text/html,<body>hi</body>", "data:text/html,<body>hi</body>"],
+ // Nested things get confusing because some things don't parse as URIs:
+ ["javascript:javascript:alert('hi!')", "alert('hi!')"],
+ ["data:data:text/html,<body>hi</body>", "data:data:text/html,<body>hi</body>"],
+ ["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
+ ["javascript:data:text/html,javascript:alert('hi!')", "data:text/html,javascript:alert('hi!')"],
+ ["data:data:text/html,javascript:alert('hi!')", "data:data:text/html,javascript:alert('hi!')"],
+];
+
+var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+
+function paste(input, cb) {
+ waitForClipboard(input, function() {
+ clipboardHelper.copyString(input);
+ }, function() {
+ document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ cb();
+ }, function() {
+ ok(false, "Failed to copy string '" + input + "' to clipboard");
+ cb();
+ });
+}
+
+function testNext() {
+ gURLBar.value = '';
+ if (!pairs.length) {
+ finish();
+ return;
+ }
+
+ let [inputValue, expectedURL] = pairs.shift();
+
+ gURLBar.focus();
+ paste(inputValue, function() {
+ is(gURLBar.textValue, expectedURL, "entering '" + inputValue + "' strips relevant bits.");
+
+ setTimeout(testNext, 0);
+ });
+}
+
diff --git a/browser/base/content/test/urlbar/browser_search_favicon.js b/browser/base/content/test/urlbar/browser_search_favicon.js
new file mode 100644
index 000000000..a8e6dbbcd
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_search_favicon.js
@@ -0,0 +1,52 @@
+var gOriginalEngine;
+var gEngine;
+var gRestyleSearchesPref = "browser.urlbar.restyleSearches";
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(gRestyleSearchesPref);
+ Services.search.currentEngine = gOriginalEngine;
+ Services.search.removeEngine(gEngine);
+ return PlacesTestUtils.clearHistory();
+});
+
+add_task(function*() {
+ Services.prefs.setBoolPref(gRestyleSearchesPref, true);
+});
+
+add_task(function*() {
+
+ Services.search.addEngineWithDetails("SearchEngine", "", "", "",
+ "GET", "http://s.example.com/search");
+ gEngine = Services.search.getEngineByName("SearchEngine");
+ gEngine.addParam("q", "{searchTerms}", null);
+ gOriginalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = gEngine;
+
+ let uri = NetUtil.newURI("http://s.example.com/search?q=foo&client=1");
+ yield PlacesTestUtils.addVisits({ uri: uri, title: "Foo - SearchEngine Search" });
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ // The first autocomplete result has the action searchengine, while
+ // the second result is the "search favicon" element.
+ yield promiseAutocompleteResultPopup("foo");
+ let result = gURLBar.popup.richlistbox.children[1];
+
+ isnot(result, null, "Expect a search result");
+ is(result.getAttribute("type"), "searchengine", "Expect correct `type` attribute");
+
+ let titleHbox = result._titleText.parentNode.parentNode;
+ ok(titleHbox.classList.contains("ac-title"), "Title hbox sanity check");
+ is_element_visible(titleHbox, "Title element should be visible");
+
+ let urlHbox = result._urlText.parentNode.parentNode;
+ ok(urlHbox.classList.contains("ac-url"), "URL hbox sanity check");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+
+ let actionHbox = result._actionText.parentNode.parentNode;
+ ok(actionHbox.classList.contains("ac-action"), "Action hbox sanity check");
+ is_element_hidden(actionHbox, "Action element should be hidden because it is not selected");
+ is(result._actionText.textContent, "Search with SearchEngine", "Action text should be as expected");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js
new file mode 100644
index 000000000..d207092d4
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js
@@ -0,0 +1,216 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+const TEST_URL_BASES = [
+ "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#tabmatch",
+ "http://example.org/browser/browser/base/content/test/urlbar/moz.png#tabmatch"
+];
+
+var gController = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+
+var gTabCounter = 0;
+
+add_task(function* step_1() {
+ info("Running step 1");
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ let promises = [];
+ for (let i = 0; i < maxResults - 1; i++) {
+ let tab = gBrowser.addTab();
+ promises.push(loadTab(tab, TEST_URL_BASES[0] + (++gTabCounter)));
+ }
+
+ yield Promise.all(promises);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_2() {
+ info("Running step 2");
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+
+ let promises = [];
+ for (let i = 1; i < gBrowser.tabs.length; i++)
+ promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[1] + (++gTabCounter)));
+
+ yield Promise.all(promises);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_3() {
+ info("Running step 3");
+ let promises = [];
+ for (let i = 1; i < gBrowser.tabs.length; i++)
+ promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter));
+
+ yield Promise.all(promises);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_4() {
+ info("Running step 4 - ensure we don't register subframes as open pages");
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.loadURI('data:text/html,<body><iframe src=""></iframe></body>');
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
+ let iframe_loaded = ContentTaskUtils.waitForEvent(content.document, "load", true);
+ content.document.querySelector("iframe").src = "http://test2.example.org/";
+ yield iframe_loaded;
+ });
+
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_5() {
+ info("Running step 5 - remove tab immediately");
+ let tab = gBrowser.addTab("about:logo");
+ yield BrowserTestUtils.removeTab(tab);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_6() {
+ info("Running step 6 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result");
+ let tabToKeep = gBrowser.addTab();
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.loadURI("about:mozilla");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ gBrowser.updateBrowserRemoteness(tabToKeep.linkedBrowser, tab.linkedBrowser.isRemoteBrowser);
+ gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
+
+ yield ensure_opentabs_match_db()
+
+ yield BrowserTestUtils.removeTab(tabToKeep);
+
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_7() {
+ info("Running step 7 - close all tabs");
+
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
+ gBrowser.addTab("about:blank", {skipAnimation: true});
+ while (gBrowser.tabs.length > 1) {
+ info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec);
+ gBrowser.selectTabAtIndex(0);
+ gBrowser.removeCurrentTab();
+ }
+
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* cleanup() {
+ info("Cleaning up");
+
+ yield PlacesTestUtils.clearHistory();
+});
+
+function loadTab(tab, url) {
+ // Because adding visits is async, we will not be notified immediately.
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let visited = new Promise(resolve => {
+ Services.obs.addObserver(
+ function observer(aSubject, aTopic, aData) {
+ if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
+ return;
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ },
+ "uri-visit-saved",
+ false
+ );
+ });
+
+ info("Loading page: " + url);
+ tab.linkedBrowser.loadURI(url);
+ return Promise.all([ loaded, visited ]);
+}
+
+function ensure_opentabs_match_db() {
+ var tabs = {};
+
+ var winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let browserWin = winEnum.getNext();
+ // skip closed-but-not-destroyed windows
+ if (browserWin.closed)
+ continue;
+
+ for (let i = 0; i < browserWin.gBrowser.tabContainer.childElementCount; i++) {
+ let browser = browserWin.gBrowser.getBrowserAtIndex(i);
+ let url = browser.currentURI.spec;
+ if (browserWin.isBlankPageURL(url))
+ continue;
+ if (!(url in tabs))
+ tabs[url] = 1;
+ else
+ tabs[url]++;
+ }
+ }
+
+ return new Promise(resolve => {
+ checkAutocompleteResults(tabs, resolve);
+ });
+}
+
+function checkAutocompleteResults(aExpected, aCallback)
+{
+ gController.input = {
+ timeout: 10,
+ textValue: "",
+ searches: ["unifiedcomplete"],
+ searchParam: "enable-actions",
+ popupOpen: false,
+ minResultsForPopup: 0,
+ invalidate: function() {},
+ disableAutoComplete: false,
+ completeDefaultIndex: false,
+ get popup() { return this; },
+ onSearchBegin: function() {},
+ onSearchComplete: function ()
+ {
+ info("Found " + gController.matchCount + " matches.");
+ // Check to see the expected uris and titles match up (in any order)
+ for (let i = 0; i < gController.matchCount; i++) {
+ if (gController.getStyleAt(i).includes("heuristic")) {
+ info("Skip heuristic match");
+ continue;
+ }
+ let action = gURLBar.popup.input._parseActionUrl(gController.getValueAt(i));
+ let uri = action.params.url;
+
+ info("Search for '" + uri + "' in open tabs.");
+ let expected = uri in aExpected;
+ ok(expected, uri + " was found in autocomplete, was " + (expected ? "" : "not ") + "expected");
+ // Remove the found entry from expected results.
+ delete aExpected[uri];
+ }
+
+ // Make sure there is no reported open page that is not open.
+ for (let entry in aExpected) {
+ ok(false, "'" + entry + "' should be found in autocomplete");
+ }
+
+ executeSoon(aCallback);
+ },
+ setSelectedIndex: function() {},
+ get searchCount() { return this.searches.length; },
+ getSearchAt: function(aIndex) { return this.searches[aIndex]; },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteInput,
+ Ci.nsIAutoCompletePopup,
+ ])
+ };
+
+ info("Searching open pages.");
+ gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
+}
diff --git a/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js
new file mode 100644
index 000000000..08a18b38a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js
@@ -0,0 +1,84 @@
+let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+add_task(function*() {
+ let normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield runTest(normalWindow, privateWindow, false);
+ yield BrowserTestUtils.closeWindow(normalWindow);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+
+ normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield runTest(privateWindow, normalWindow, false);
+ yield BrowserTestUtils.closeWindow(normalWindow);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+
+ privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield runTest(privateWindow, privateWindow, false);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+
+ normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ yield runTest(normalWindow, normalWindow, true);
+ yield BrowserTestUtils.closeWindow(normalWindow);
+});
+
+function* runTest(aSourceWindow, aDestWindow, aExpectSwitch, aCallback) {
+ yield BrowserTestUtils.openNewForegroundTab(aSourceWindow.gBrowser, testURL);
+ let testTab = yield BrowserTestUtils.openNewForegroundTab(aDestWindow.gBrowser);
+
+ info("waiting for focus on the window");
+ yield SimpleTest.promiseFocus(aDestWindow);
+ info("got focus on the window");
+
+ // Select the testTab
+ aDestWindow.gBrowser.selectedTab = testTab;
+
+ // Ensure that this tab has no history entries
+ let sessionHistoryCount = yield new Promise(resolve => {
+ SessionStore.getSessionHistory(gBrowser.selectedTab, function(sessionHistory) {
+ resolve(sessionHistory.entries.length);
+ });
+ });
+
+ ok(sessionHistoryCount < 2,
+ `The test tab has 1 or fewer history entries. sessionHistoryCount=${sessionHistoryCount}`);
+ // Ensure that this tab is on about:blank
+ is(testTab.linkedBrowser.currentURI.spec, "about:blank",
+ "The test tab is on about:blank");
+ // Ensure that this tab's document has no child nodes
+ yield ContentTask.spawn(testTab.linkedBrowser, null, function*() {
+ ok(!content.document.body.hasChildNodes(),
+ "The test tab has no child nodes");
+ });
+ ok(!testTab.hasAttribute("busy"),
+ "The test tab doesn't have the busy attribute");
+
+ // Wait for the Awesomebar popup to appear.
+ yield promiseAutocompleteResultPopup(testURL, aDestWindow);
+
+ info(`awesomebar popup appeared. aExpectSwitch: ${aExpectSwitch}`);
+ // Make sure the last match is selected.
+ let {controller, popup} = aDestWindow.gURLBar;
+ while (popup.selectedIndex < controller.matchCount - 1) {
+ info("handling key navigation for DOM_VK_DOWN key");
+ controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
+ }
+
+ let awaitTabSwitch;
+ if (aExpectSwitch) {
+ awaitTabSwitch = BrowserTestUtils.removeTab(testTab, {dontRemove: true})
+ }
+
+ // Execute the selected action.
+ controller.handleEnter(true);
+ info("sent Enter command to the controller");
+
+ if (aExpectSwitch) {
+ // If we expect a tab switch then the current tab
+ // will be closed and we switch to the other tab.
+ yield awaitTabSwitch;
+ } else {
+ // If we don't expect a tab switch then wait for the tab to load.
+ yield BrowserTestUtils.browserLoaded(testTab.linkedBrowser);
+ }
+}
diff --git a/browser/base/content/test/urlbar/browser_urlHighlight.js b/browser/base/content/test/urlbar/browser_urlHighlight.js
new file mode 100644
index 000000000..ba1537d91
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlHighlight.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function testVal(aExpected) {
+ gURLBar.value = aExpected.replace(/[<>]/g, "");
+
+ let selectionController = gURLBar.editor.selectionController;
+ let selection = selectionController.getSelection(selectionController.SELECTION_URLSECONDARY);
+ let value = gURLBar.editor.rootElement.textContent;
+ let result = "";
+ for (let i = 0; i < selection.rangeCount; i++) {
+ let range = selection.getRangeAt(i).toString();
+ let pos = value.indexOf(range);
+ result += value.substring(0, pos) + "<" + range + ">";
+ value = value.substring(pos + range.length);
+ }
+ result += value;
+ is(result, aExpected,
+ "Correct part of the urlbar contents is highlighted");
+}
+
+function test() {
+ const prefname = "browser.urlbar.formatting.enabled";
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(prefname);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(prefname, true);
+
+ gURLBar.focus();
+
+ testVal("https://mozilla.org");
+
+ gBrowser.selectedBrowser.focus();
+
+ testVal("<https://>mozilla.org");
+ testVal("<https://>mözilla.org");
+ testVal("<https://>mozilla.imaginatory");
+
+ testVal("<https://www.>mozilla.org");
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<www.>mozilla.org");
+ testVal("<sub.>mozilla.org");
+ testVal("<sub1.sub2.sub3.>mozilla.org");
+ testVal("<mozilla.com.>mozilla.com");
+ testVal("<https://mozilla.com:mozilla.com@>mozilla.com");
+ testVal("<mozilla.com:mozilla.com@>mozilla.com");
+
+ testVal("<ftp.>mozilla.org");
+ testVal("<ftp://ftp.>mozilla.org");
+
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@>mozilla.org");
+ testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<user:pass@>mozilla.org");
+
+ testVal("<https://>mozilla.org< >");
+ testVal("mozilla.org< >");
+
+ testVal("<https://>mozilla.org</file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("foo.bar<?q=test>");
+ testVal("foo.bar<#mozilla.org>");
+ testVal("foo.bar<?somewhere.mozilla.org>");
+ testVal("foo.bar<?@mozilla.org>");
+ testVal("foo.bar<#x@mozilla.org>");
+ testVal("foo.bar<#@x@mozilla.org>");
+ testVal("foo.bar<?x@mozilla.org>");
+ testVal("foo.bar<?@x@mozilla.org>");
+ testVal("<foo.bar@x@>mozilla.org");
+ testVal("<foo.bar@:baz@>mozilla.org");
+ testVal("<foo.bar:@baz@>mozilla.org");
+ testVal("<foo.bar@:ba:z@>mozilla.org");
+ testVal("<foo.:bar:@baz@>mozilla.org");
+
+ testVal("<https://sub.>mozilla.org<:666/file.ext>");
+ testVal("<sub.>mozilla.org<:666/file.ext>");
+ testVal("localhost<:666/file.ext>");
+
+ let IPs = ["192.168.1.1",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[1:2:3:4:5:6:7::]",
+ "[::1:2:3:4:5:6:7]",
+ "[1:2:a:B:c:D:e:F]",
+ "[1::8]",
+ "[1:2::8]",
+ "[fe80::222:19ff:fe11:8c76]",
+ "[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]",
+ "[::192.168.1.1]",
+ "[1::0.0.0.0]",
+ "[1:2::255.255.255.255]",
+ "[1:2:3::255.255.255.255]",
+ "[1:2:3:4::255.255.255.255]",
+ "[1:2:3:4:5::255.255.255.255]",
+ "[1:2:3:4:5:6:255.255.255.255]"];
+ IPs.forEach(function (IP) {
+ testVal(IP);
+ testVal(IP + "</file.ext>");
+ testVal(IP + "<:666/file.ext>");
+ testVal("<https://>" + IP);
+ testVal("<https://>" + IP + "</file.ext>");
+ testVal("<https://user:pass@>" + IP + "<:666/file.ext>");
+ testVal("<user:pass@>" + IP + "<:666/file.ext>");
+ });
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+ testVal("foo9://mozilla.org/");
+ testVal("foo+://mozilla.org/");
+ testVal("foo.://mozilla.org/");
+ testVal("foo-://mozilla.org/");
+
+ Services.prefs.setBoolPref(prefname, false);
+
+ testVal("https://mozilla.org");
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
new file mode 100644
index 000000000..792826eb1
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+/**
+ * Test what happens if loading a URL that should clear the
+ * location bar after a parent process URL.
+ */
+add_task(function* clearURLBarAfterParentProcessURL() {
+ let tab = yield new Promise(resolve => {
+ gBrowser.selectedTab = gBrowser.addTab("about:preferences");
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function onInit() {
+ newTabBrowser.removeEventListener("Initialized", onInit, true);
+ resolve(gBrowser.selectedTab);
+ }, true);
+ });
+ document.getElementById("home-button").click();
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Same as above, but open the tab without passing the URL immediately
+ * which changes behaviour in tabbrowser.xml.
+ */
+add_task(function* clearURLBarAfterParentProcessURLInExistingTab() {
+ let tab = yield new Promise(resolve => {
+ gBrowser.selectedTab = gBrowser.addTab();
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function onInit() {
+ newTabBrowser.removeEventListener("Initialized", onInit, true);
+ resolve(gBrowser.selectedTab);
+ }, true);
+ newTabBrowser.loadURI("about:preferences");
+ });
+ document.getElementById("home-button").click();
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Load about:home directly from an about:newtab page. Because it is an
+ * 'initial' page, we need to treat this specially if the user actually
+ * loads a page like this from the URL bar.
+ */
+add_task(function* clearURLBarAfterManuallyLoadingAboutHome() {
+ let promiseTabOpenedAndSwitchedTo = BrowserTestUtils.switchTab(gBrowser, () => {});
+ // This opens about:newtab:
+ BrowserOpenTab();
+ let tab = yield promiseTabOpenedAndSwitchedTo;
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+
+ gURLBar.value = "about:home";
+ gURLBar.select();
+ let aboutHomeLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:home");
+ EventUtils.sendKey("return");
+ yield aboutHomeLoaded;
+
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Ensure we don't show 'about:home' in the URL bar temporarily in new tabs
+ * while we're switching remoteness (when the URL we're loading and the
+ * default content principal are different).
+ */
+add_task(function* dontTemporarilyShowAboutHome() {
+ yield SpecialPowers.pushPrefEnv({set: [["browser.startup.page", 1]]});
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ let win = OpenBrowserWindow();
+ yield windowOpenedPromise;
+ let promiseTabSwitch = BrowserTestUtils.switchTab(win.gBrowser, () => {});
+ win.BrowserOpenTab();
+ yield promiseTabSwitch;
+ yield TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.closeWindow(win);
+ ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
+
+ windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ win = SessionStore.undoCloseWindow(0);
+ yield windowOpenedPromise;
+ let wpl = {
+ onLocationChange(wpl, request, location, flags) {
+ is(win.gURLBar.value, "", "URL bar value should stay empty.");
+ },
+ };
+ win.gBrowser.addProgressListener(wpl);
+ let otherTab = win.gBrowser.selectedTab.previousSibling;
+ let tabLoaded = BrowserTestUtils.browserLoaded(otherTab.linkedBrowser, false, "about:home");
+ yield BrowserTestUtils.switchTab(win.gBrowser, otherTab);
+ yield tabLoaded;
+ win.gBrowser.removeProgressListener(wpl);
+ is(win.gURLBar.value, "", "URL bar value should be empty.");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
new file mode 100644
index 000000000..8101c101d
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
@@ -0,0 +1,49 @@
+// This test ensures that autoFilled values are not trimmed, unless the user
+// selects from the autocomplete popup.
+
+add_task(function* setup() {
+ const PREF_TRIMURL = "browser.urlbar.trimURLs";
+ const PREF_AUTOFILL = "browser.urlbar.autoFill";
+
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(PREF_TRIMURL);
+ Services.prefs.clearUserPref(PREF_AUTOFILL);
+ yield PlacesTestUtils.clearHistory();
+ gURLBar.handleRevert();
+ });
+ Services.prefs.setBoolPref(PREF_TRIMURL, true);
+ Services.prefs.setBoolPref(PREF_AUTOFILL, true);
+
+ // Adding a tab would hit switch-to-tab, so it's safer to just add a visit.
+ yield PlacesTestUtils.addVisits({
+ uri: "http://www.autofilltrimurl.com/whatever",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+});
+
+function* promiseSearch(searchtext) {
+ gURLBar.focus();
+ gURLBar.inputField.value = searchtext.substr(0, searchtext.length -1);
+ EventUtils.synthesizeKey(searchtext.substr(-1, 1), {});
+ yield promiseSearchComplete();
+}
+
+add_task(function* () {
+ yield promiseSearch("http://");
+ is(gURLBar.inputField.value, "http://", "Autofilled value is as expected");
+});
+
+add_task(function* () {
+ yield promiseSearch("http://au");
+ is(gURLBar.inputField.value, "http://autofilltrimurl.com/", "Autofilled value is as expected");
+});
+
+add_task(function* () {
+ yield promiseSearch("http://www.autofilltrimurl.com");
+ is(gURLBar.inputField.value, "http://www.autofilltrimurl.com/", "Autofilled value is as expected");
+
+ // Now ensure selecting from the popup correctly trims.
+ is(gURLBar.controller.matchCount, 2, "Found the expected number of matches");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(gURLBar.inputField.value, "www.autofilltrimurl.com/whatever", "trim was applied correctly");
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarCopying.js b/browser/base/content/test/urlbar/browser_urlbarCopying.js
new file mode 100644
index 000000000..8d5562b61
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js
@@ -0,0 +1,232 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const trimPref = "browser.urlbar.trimURLs";
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function toUnicode(input) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ return converter.ConvertToUnicode(input);
+}
+
+function test() {
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ Services.prefs.clearUserPref(trimPref);
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(trimPref, true);
+ Services.prefs.setIntPref(phishyUserPassPref, 32); // avoid prompting about phishing
+
+ waitForExplicitFinish();
+
+ nextTest();
+}
+
+var tests = [
+ // pageproxystate="invalid"
+ {
+ setURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "example.com"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ // pageproxystate="valid" from this point on (due to the load)
+ {
+ loadURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "http://example.com/"
+ },
+ {
+ copyVal: "<example.co>m",
+ copyExpected: "example.co"
+ },
+ {
+ copyVal: "e<x>ample.com",
+ copyExpected: "x"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ {
+ loadURL: "http://example.com/foo",
+ expectedURL: "example.com/foo",
+ copyExpected: "http://example.com/foo"
+ },
+ {
+ copyVal: "<example.com>/foo",
+ copyExpected: "http://example.com"
+ },
+ {
+ copyVal: "<example>.com/foo",
+ copyExpected: "example"
+ },
+
+ // Test that userPass is stripped out
+ {
+ loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass",
+ expectedURL: "mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass",
+ copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass"
+ },
+
+ // Test escaping
+ {
+ loadURL: "http://example.com/()%28%29%C3%A9",
+ expectedURL: "example.com/()()\xe9",
+ copyExpected: "http://example.com/()%28%29%C3%A9"
+ },
+ {
+ copyVal: "<example.com/(>)()\xe9",
+ copyExpected: "http://example.com/("
+ },
+ {
+ copyVal: "e<xample.com/(>)()\xe9",
+ copyExpected: "xample.com/("
+ },
+
+ {
+ loadURL: "http://example.com/%C3%A9%C3%A9",
+ expectedURL: "example.com/\xe9\xe9",
+ copyExpected: "http://example.com/%C3%A9%C3%A9"
+ },
+ {
+ copyVal: "e<xample.com/\xe9>\xe9",
+ copyExpected: "xample.com/\xe9"
+ },
+ {
+ copyVal: "<example.com/\xe9>\xe9",
+ copyExpected: "http://example.com/\xe9"
+ },
+
+ {
+ loadURL: "http://example.com/?%C3%B7%C3%B7",
+ expectedURL: "example.com/?\xf7\xf7",
+ copyExpected: "http://example.com/?%C3%B7%C3%B7"
+ },
+ {
+ copyVal: "e<xample.com/?\xf7>\xf7",
+ copyExpected: "xample.com/?\xf7"
+ },
+ {
+ copyVal: "<example.com/?\xf7>\xf7",
+ copyExpected: "http://example.com/?\xf7"
+ },
+ {
+ loadURL: "http://example.com/a%20test",
+ expectedURL: "example.com/a test",
+ copyExpected: "http://example.com/a%20test"
+ },
+ {
+ loadURL: "http://example.com/a%E3%80%80test",
+ expectedURL: toUnicode("example.com/a test"),
+ copyExpected: "http://example.com/a%E3%80%80test"
+ },
+ {
+ loadURL: "http://example.com/a%20%C2%A0test",
+ expectedURL: "example.com/a%20%C2%A0test",
+ copyExpected: "http://example.com/a%20%C2%A0test"
+ },
+ {
+ loadURL: "http://example.com/%20%20%20",
+ expectedURL: "example.com/%20%20%20",
+ copyExpected: "http://example.com/%20%20%20"
+ },
+ {
+ loadURL: "http://example.com/%E3%80%80%E3%80%80",
+ expectedURL: "example.com/%E3%80%80%E3%80%80",
+ copyExpected: "http://example.com/%E3%80%80%E3%80%80"
+ },
+
+ // data: and javsacript: URIs shouldn't be encoded
+ {
+ loadURL: "javascript:('%C3%A9%20%25%50')",
+ expectedURL: "javascript:('%C3%A9 %25P')",
+ copyExpected: "javascript:('%C3%A9 %25P')"
+ },
+ {
+ copyVal: "<javascript:(>'%C3%A9 %25P')",
+ copyExpected: "javascript:("
+ },
+
+ {
+ loadURL: "data:text/html,(%C3%A9%20%25%50)",
+ expectedURL: "data:text/html,(%C3%A9 %25P)",
+ copyExpected: "data:text/html,(%C3%A9 %25P)",
+ },
+ {
+ copyVal: "<data:text/html,(>%C3%A9 %25P)",
+ copyExpected: "data:text/html,("
+ },
+ {
+ copyVal: "<data:text/html,(%C3%A9 %25P>)",
+ copyExpected: "data:text/html,(%C3%A9 %25P",
+ }
+];
+
+function nextTest() {
+ let test = tests.shift();
+ if (tests.length == 0)
+ runTest(test, finish);
+ else
+ runTest(test, nextTest);
+}
+
+function runTest(test, cb) {
+ function doCheck() {
+ if (test.setURL || test.loadURL) {
+ gURLBar.valueIsTyped = !!test.setURL;
+ is(gURLBar.textValue, test.expectedURL, "url bar value set");
+ }
+
+ testCopy(test.copyVal, test.copyExpected, cb);
+ }
+
+ if (test.loadURL) {
+ loadURL(test.loadURL, doCheck);
+ } else {
+ if (test.setURL)
+ gURLBar.value = test.setURL;
+ doCheck();
+ }
+}
+
+function testCopy(copyVal, targetValue, cb) {
+ info("Expecting copy of: " + targetValue);
+ waitForClipboard(targetValue, function () {
+ gURLBar.focus();
+ if (copyVal) {
+ let startBracket = copyVal.indexOf("<");
+ let endBracket = copyVal.indexOf(">");
+ if (startBracket == -1 || endBracket == -1 ||
+ startBracket > endBracket ||
+ copyVal.replace("<", "").replace(">", "") != gURLBar.textValue) {
+ ok(false, "invalid copyVal: " + copyVal);
+ }
+ gURLBar.selectionStart = startBracket;
+ gURLBar.selectionEnd = endBracket - 1;
+ } else {
+ gURLBar.select();
+ }
+
+ goDoCommand("cmd_copy");
+ }, cb, cb);
+}
+
+function loadURL(aURL, aCB) {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, aURL);
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, aURL).then(aCB);
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarDecode.js b/browser/base/content/test/urlbar/browser_urlbarDecode.js
new file mode 100644
index 000000000..6a2c421ef
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarDecode.js
@@ -0,0 +1,97 @@
+"use strict";
+
+// This test makes sure (1) you can't break the urlbar by typing particular JSON
+// or JS fragments into it, (2) urlbar.textValue shows URLs unescaped, and (3)
+// the urlbar also shows the URLs embedded in action URIs unescaped. See bug
+// 1233672.
+
+add_task(function* injectJSON() {
+ let inputStrs = [
+ 'http://example.com/ ", "url": "bar',
+ 'http://example.com/\\',
+ 'http://example.com/"',
+ 'http://example.com/","url":"evil.com',
+ 'http://mozilla.org/\\u0020',
+ 'http://www.mozilla.org/","url":1e6,"some-key":"foo',
+ 'http://www.mozilla.org/","url":null,"some-key":"foo',
+ 'http://www.mozilla.org/","url":["foo","bar"],"some-key":"foo',
+ ];
+ for (let inputStr of inputStrs) {
+ yield checkInput(inputStr);
+ }
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(function losslessDecode() {
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
+ gURLBar.textValue = url;
+ // Since this is directly setting textValue, it is expected to be trimmed.
+ Assert.equal(gURLBar.inputField.value, urlNoScheme,
+ "The string displayed in the textbox should not be escaped");
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(function* actionURILosslessDecode() {
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
+ yield promiseAutocompleteResultPopup(url);
+
+ // At this point the heuristic result is selected but the urlbar's value is
+ // simply `url`. Key down and back around until the heuristic result is
+ // selected again, and at that point the urlbar's value should be a visiturl
+ // moz-action.
+
+ do {
+ gURLBar.controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
+ } while (gURLBar.popup.selectedIndex != 0);
+
+ let [, type, ] = gURLBar.value.match(/^moz-action:([^,]+),(.*)$/);
+ Assert.equal(type, "visiturl",
+ "visiturl action URI should be in the urlbar");
+
+ Assert.equal(gURLBar.inputField.value, urlNoScheme,
+ "The string displayed in the textbox should not be escaped");
+
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+function* checkInput(inputStr) {
+ yield promiseAutocompleteResultPopup(inputStr);
+
+ let item = gURLBar.popup.richlistbox.firstChild;
+ Assert.ok(item, "Should have a result");
+
+ // visiturl matches have their param.urls fixed up.
+ let fixupInfo = Services.uriFixup.getFixupURIInfo(inputStr,
+ Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ );
+ let expectedVisitURL = fixupInfo.fixedURI.spec;
+
+ let type = "visiturl";
+ let params = {
+ url: expectedVisitURL,
+ input: inputStr,
+ };
+ for (let key in params) {
+ params[key] = encodeURIComponent(params[key]);
+ }
+ let expectedURL = "moz-action:" + type + "," + JSON.stringify(params);
+ Assert.equal(item.getAttribute("url"), expectedURL, "url");
+
+ Assert.equal(item.getAttribute("title"), inputStr.replace("\\", "/"), "title");
+ Assert.equal(item.getAttribute("text"), inputStr, "text");
+
+ let itemType = item.getAttribute("type");
+ Assert.equal(itemType, "visiturl");
+
+ Assert.equal(item._titleText.textContent, inputStr.replace("\\", "/"), "Visible title");
+ Assert.equal(item._actionText.textContent, "Visit", "Visible action");
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarDelete.js b/browser/base/content/test/urlbar/browser_urlbarDelete.js
new file mode 100644
index 000000000..d4eb6c856
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarDelete.js
@@ -0,0 +1,39 @@
+add_task(function*() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://bug1105244.example.com/",
+ title: "test" });
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, testDelete);
+});
+
+function sendHome() {
+ // unclear why VK_HOME doesn't work on Mac, but it doesn't...
+ if (Services.appinfo.OS == "Darwin") {
+ EventUtils.synthesizeKey("VK_LEFT", { altKey: true });
+ } else {
+ EventUtils.synthesizeKey("VK_HOME", {});
+ }
+}
+
+function sendDelete() {
+ EventUtils.synthesizeKey("VK_DELETE", {});
+}
+
+function* testDelete() {
+ yield promiseAutocompleteResultPopup("bug1105244");
+
+ // move to the start.
+ sendHome();
+ // delete the first few chars - each delete should operate on the input field.
+ sendDelete();
+ Assert.equal(gURLBar.inputField.value, "ug1105244");
+
+ yield promisePopupShown(gURLBar.popup);
+
+ sendDelete();
+ Assert.equal(gURLBar.inputField.value, "g1105244");
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarEnter.js b/browser/base/content/test/urlbar/browser_urlbarEnter.js
new file mode 100644
index 000000000..32cbaf2be
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarEnter.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_VALUE = "example.com/\xF7?\xF7";
+const START_VALUE = "example.com/%C3%B7?%C3%B7";
+
+add_task(function* () {
+ info("Simple return keypress");
+ let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE);
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ // Check url bar and selected tab.
+ is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress");
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ // Cleanup.
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* () {
+ info("Alt+Return keypress");
+ // due to bug 691608, we must wait for the load event, else isTabEmpty() will
+ // return true on e10s for this tab, so it will be reused even with altKey.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE);
+
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
+
+ // wait for the new tab to appear.
+ yield tabOpenPromise;
+
+ // Check url bar and selected tab.
+ is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress");
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ yield BrowserTestUtils.removeTab(tab);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js b/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js
new file mode 100644
index 000000000..22e336f91
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js
@@ -0,0 +1,69 @@
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+function* promiseAutoComplete(inputText) {
+ gURLBar.focus();
+ gURLBar.value = inputText.slice(0, -1);
+ EventUtils.synthesizeKey(inputText.slice(-1), {});
+ yield promiseSearchComplete();
+}
+
+function is_selected(index) {
+ is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
+}
+
+let gMaxResults;
+
+add_task(function*() {
+ registerCleanupFunction(function* () {
+ yield PlacesTestUtils.clearHistory();
+ });
+
+ yield PlacesTestUtils.clearHistory();
+
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ let visits = [];
+ repeat(gMaxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ yield PlacesTestUtils.addVisits(visits);
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseAutoComplete("http://example.com/autocomplete/");
+
+ let popup = gURLBar.popup;
+ let results = popup.richlistbox.children;
+ is(results.length, gMaxResults,
+ "Should get gMaxResults=" + gMaxResults + " results");
+
+ let initiallySelected = gURLBar.popup.richlistbox.selectedIndex;
+
+ info("Key Down to select the next item");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is_selected(initiallySelected + 1);
+ let expectedURL = gURLBar.controller.getFinalCompleteValueAt(initiallySelected + 1);
+
+ is(gURLBar.value, gURLBar.controller.getValueAt(initiallySelected + 1),
+ "Value in the URL bar should be updated by keyboard selection");
+
+ // Verify that what we're about to do changes the selectedIndex:
+ isnot(initiallySelected + 1, 3, "Shouldn't be changing the selectedIndex to the same index we keyboard-selected.");
+
+ // Would love to use a synthetic mousemove event here, but that doesn't seem to do anything.
+ // EventUtils.synthesizeMouseAtCenter(results[3], {type: "mousemove"});
+ gURLBar.popup.richlistbox.selectedIndex = 3;
+ is_selected(3);
+
+ let autocompletePopupHidden = promisePopupHidden(gURLBar.popup);
+ let openedExpectedPage = waitForDocLoadAndStopIt(expectedURL);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield Promise.all([autocompletePopupHidden, openedExpectedPage]);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js
new file mode 100644
index 000000000..8c9e2c9f2
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function*() {
+ // Remove the search bar from toolbar
+ CustomizableUI.removeWidgetFromArea("search-container");
+
+ // Test that Ctrl/Cmd + K will focus the url bar
+ let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ yield focusPromise;
+ Assert.equal(document.activeElement, gURLBar.inputField, "URL Bar should be focused");
+
+ // Reset changes made to toolbar
+ CustomizableUI.reset();
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js b/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js
new file mode 100644
index 000000000..152106dad
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js
@@ -0,0 +1,111 @@
+"use strict";
+
+/**
+ * Check that navigating through both the URL bar and using in-page hash- or ref-
+ * based links and back or forward navigation updates the URL bar and identity block correctly.
+ */
+add_task(function* () {
+ let baseURL = "https://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+ let url = baseURL + "#foo";
+ yield BrowserTestUtils.withNewTab({ gBrowser, url }, function*(browser) {
+ let identityBox = document.getElementById("identity-box");
+ let expectedURL = url;
+
+ let verifyURLBarState = testType => {
+ is(gURLBar.textValue, expectedURL, "URL bar visible value should be correct " + testType);
+ is(gURLBar.value, expectedURL, "URL bar value should be correct " + testType);
+ ok(identityBox.classList.contains("verifiedDomain"), "Identity box should know we're doing SSL " + testType);
+ is(gURLBar.getAttribute("pageproxystate"), "valid", "URL bar is in valid page proxy state");
+ };
+
+ verifyURLBarState("at the beginning");
+
+ let locationChangePromise;
+ let resolveLocationChangePromise;
+ let expectURL = url => {
+ expectedURL = url;
+ locationChangePromise = new Promise(r => resolveLocationChangePromise = r);
+ };
+ let wpl = {
+ onLocationChange(wpl, request, location, flags) {
+ is(location.spec, expectedURL, "Got the expected URL");
+ resolveLocationChangePromise();
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ expectURL(baseURL + "#foo");
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+ verifyURLBarState("after hitting enter on the same URL a second time");
+
+ expectURL(baseURL + "#bar");
+ gURLBar.value = expectedURL;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+ verifyURLBarState("after a URL bar hash navigation");
+
+ expectURL(baseURL + "#foo");
+ yield ContentTask.spawn(browser, null, function() {
+ let a = content.document.createElement("a");
+ a.href = "#foo";
+ a.textContent = "Foo Link";
+ content.document.body.appendChild(a);
+ a.click();
+ });
+
+ yield locationChangePromise;
+ verifyURLBarState("after a page link hash navigation");
+
+ expectURL(baseURL + "#bar");
+ gBrowser.goBack();
+
+ yield locationChangePromise;
+ verifyURLBarState("after going back");
+
+ expectURL(baseURL + "#foo");
+ gBrowser.goForward();
+
+ yield locationChangePromise;
+ verifyURLBarState("after going forward");
+
+ expectURL(baseURL + "#foo");
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+ verifyURLBarState("after hitting enter on the same URL");
+
+ gBrowser.removeProgressListener(wpl);
+ });
+});
+
+/**
+ * Check that initial secure loads that swap remoteness
+ * get the correct page icon when finished.
+ */
+add_task(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ // NB: CPOW usage because new tab pages can be preloaded, in which case no
+ // load events fire.
+ yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
+ let url = "https://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#foo";
+ gURLBar.value = url;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ is(gURLBar.textValue, url, "URL bar visible value should be correct when the page loads from about:newtab");
+ is(gURLBar.value, url, "URL bar value should be correct when the page loads from about:newtab");
+ let identityBox = document.getElementById("identity-box");
+ ok(identityBox.classList.contains("verifiedDomain"),
+ "Identity box should know we're doing SSL when the page loads from about:newtab");
+ is(gURLBar.getAttribute("pageproxystate"), "valid",
+ "URL bar is in valid page proxy state when SSL page with hash loads from about:newtab");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js
new file mode 100644
index 000000000..9c8996059
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js
@@ -0,0 +1,49 @@
+"use strict";
+
+/**
+ * Verify user typed text remains in the URL bar when tab switching, even when
+ * loads fail.
+ */
+add_task(function* () {
+ let input = "i-definitely-dont-exist.example.com";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ // NB: CPOW usage because new tab pages can be preloaded, in which case no
+ // load events fire.
+ yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ gURLBar.value = input;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ yield errorPageLoaded;
+ is(gURLBar.textValue, input, "Text is still in URL bar");
+ yield BrowserTestUtils.switchTab(gBrowser, tab.previousSibling);
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.textValue, input, "Text is still in URL bar after tab switch");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Invalid URIs fail differently (that is, immediately, in the loadURI call)
+ * if keyword searches are turned off. Test that this works, too.
+ */
+add_task(function* () {
+ let input = "To be or not to be-that is the question";
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]}, resolve));
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ // NB: CPOW usage because new tab pages can be preloaded, in which case no
+ // load events fire.
+ yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ gURLBar.value = input;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ yield errorPageLoaded;
+ is(gURLBar.textValue, input, "Text is still in URL bar");
+ is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser");
+ yield BrowserTestUtils.switchTab(gBrowser, tab.previousSibling);
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.textValue, input, "Text is still in URL bar after tab switch");
+ is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
new file mode 100644
index 000000000..1f58b8edd
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -0,0 +1,232 @@
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+let gMaxResults;
+
+add_task(function* init() {
+ Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ // Add a search suggestion engine and move it to the front so that it appears
+ // as the first one-off.
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ Services.search.moveEngine(engine, 0);
+
+ registerCleanupFunction(function* () {
+ yield hidePopup();
+ yield PlacesTestUtils.clearHistory();
+ });
+
+ yield PlacesTestUtils.clearHistory();
+
+ let visits = [];
+ for (let i = 0; i < gMaxResults; i++) {
+ visits.push({
+ uri: makeURI("http://example.com/browser_urlbarOneOffs.js/?" + i),
+ // TYPED so that the visit shows up when the urlbar's drop-down arrow is
+ // pressed.
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+ }
+ yield PlacesTestUtils.addVisits(visits);
+});
+
+// Keys up and down through the history panel, i.e., the panel that's shown when
+// there's no text in the textbox.
+add_task(function* history() {
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ yield promisePopupShown(gURLBar.popup);
+
+ assertState(-1, -1, "");
+
+ // Key down through each result.
+ for (let i = 0; i < gMaxResults; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(i, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key down through each one-off.
+ let numButtons =
+ gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
+ for (let i = 0; i < numButtons; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(-1, i, "");
+ }
+
+ // Key down once more. Nothing should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(-1, -1, "");
+
+ // Once more. The first result should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(0, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - 1));
+
+ // Now key up. Nothing should be selected again.
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, -1, "");
+
+ // Key up through each one-off.
+ for (let i = numButtons - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, i, "");
+ }
+
+ // Key up through each result.
+ for (let i = gMaxResults - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(i, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key up once more. Nothing should be selected.
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, -1, "");
+
+ yield hidePopup();
+});
+
+// Keys up and down through the non-history panel, i.e., the panel that's shown
+// when you type something in the textbox.
+add_task(function* typedValue() {
+ // Use a typed value that returns the visits added above but that doesn't
+ // trigger autofill since that would complicate the test.
+ let typedValue = "browser_urlbarOneOffs";
+ yield promiseAutocompleteResultPopup(typedValue, window, true);
+
+ assertState(0, -1, typedValue);
+
+ // Key down through each result. The first result is already selected, which
+ // is why gMaxResults - 1 is the correct number of times to do this.
+ for (let i = 0; i < gMaxResults - 1; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ // i starts at zero so that the textValue passed to assertState is correct.
+ // But that means that i + 1 is the expected selected index, since initially
+ // (when this loop starts) the first result is selected.
+ assertState(i + 1, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key down through each one-off.
+ let numButtons =
+ gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
+ for (let i = 0; i < numButtons; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(-1, i, typedValue);
+ }
+
+ // Key down once more. The selection should wrap around to the first result.
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(0, -1, typedValue);
+
+ // Now key up. The selection should wrap back around to the one-offs. Key
+ // up through all the one-offs.
+ for (let i = numButtons - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, i, typedValue);
+ }
+
+ // Key up through each non-heuristic result.
+ for (let i = gMaxResults - 2; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(i + 1, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key up once more. The heuristic result should be selected.
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(0, -1, typedValue);
+
+ yield hidePopup();
+});
+
+// Checks that "Search with Current Search Engine" items are updated to "Search
+// with One-Off Engine" when a one-off is selected.
+add_task(function* searchWith() {
+ let typedValue = "foo";
+ yield promiseAutocompleteResultPopup(typedValue);
+
+ assertState(0, -1, typedValue);
+
+ let item = gURLBar.popup.richlistbox.firstChild;
+ Assert.equal(item._actionText.textContent,
+ "Search with " + Services.search.currentEngine.name,
+ "Sanity check: first result's action text");
+
+ // Alt+Down to the first one-off. Now the first result and the first one-off
+ // should both be selected.
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
+ assertState(0, 0, typedValue);
+
+ let engineName = gURLBar.popup.oneOffSearchButtons.selectedButton.engine.name;
+ Assert.notEqual(engineName, Services.search.currentEngine.name,
+ "Sanity check: First one-off engine should not be " +
+ "the current engine");
+ Assert.equal(item._actionText.textContent,
+ "Search with " + engineName,
+ "First result's action text should be updated");
+
+ yield hidePopup();
+});
+
+// Clicks a one-off.
+add_task(function* oneOffClick() {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+ yield promiseAutocompleteResultPopup(typedValue);
+
+ assertState(0, -1, typedValue);
+
+ let oneOffs = gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true);
+ let resultsPromise =
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+ "http://mochi.test:8888/");
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
+ yield resultsPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
+
+// Presses the Return key when a one-off is selected.
+add_task(function* oneOffReturn() {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+ yield promiseAutocompleteResultPopup(typedValue, window, true);
+
+ assertState(0, -1, typedValue);
+
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
+ assertState(0, 0, typedValue);
+
+ let resultsPromise =
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+ "http://mochi.test:8888/");
+ EventUtils.synthesizeKey("VK_RETURN", {})
+ yield resultsPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
+
+
+function assertState(result, oneOff, textValue = undefined) {
+ Assert.equal(gURLBar.popup.selectedIndex, result,
+ "Expected result should be selected");
+ Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, oneOff,
+ "Expected one-off should be selected");
+ if (textValue !== undefined) {
+ Assert.equal(gURLBar.textValue, textValue, "Expected textValue");
+ }
+}
+
+function* hidePopup() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promisePopupHidden(gURLBar.popup);
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js b/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js
new file mode 100644
index 000000000..5db0f0ea6
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js
@@ -0,0 +1,41 @@
+"use strict";
+
+/**
+ * Test that when opening a private browsing window and typing in it before about:privatebrowsing
+ * loads, we don't clear the URL bar.
+ */
+add_task(function*() {
+ let urlbarTestValue = "Mary had a little lamb";
+ let win = OpenBrowserWindow({private: true});
+ yield BrowserTestUtils.waitForEvent(win, "load");
+ let urlbar = win.document.getElementById("urlbar");
+ urlbar.value = urlbarTestValue;
+ // Need this so the autocomplete controller attaches:
+ let focusEv = new FocusEvent("focus", {});
+ urlbar.dispatchEvent(focusEv);
+ // And so we know input happened:
+ let inputEv = new InputEvent("input", {data: "", view: win, bubbles: true});
+ urlbar.onInput(inputEv);
+ // Check it worked:
+ is(urlbar.value, urlbarTestValue, "URL bar value should be there");
+ is(win.gBrowser.selectedBrowser.userTypedValue, urlbarTestValue, "browser object should know the url bar value");
+
+ let continueTest;
+ let continuePromise = new Promise(resolve => continueTest = resolve);
+ let wpl = {
+ onLocationChange(aWebProgress, aRequest, aLocation) {
+ if (aLocation && aLocation.spec == "about:privatebrowsing") {
+ continueTest();
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(wpl);
+
+ yield continuePromise;
+ is(urlbar.value, urlbarTestValue,
+ "URL bar value should be the same once about:privatebrowsing has loaded");
+ is(win.gBrowser.selectedBrowser.userTypedValue, urlbarTestValue,
+ "browser object should still know url bar value once about:privatebrowsing has loaded");
+ win.gBrowser.removeProgressListener(wpl);
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js b/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js
new file mode 100644
index 000000000..d66514c5a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js
@@ -0,0 +1,57 @@
+const kURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+function* addBookmark(bookmark) {
+ if (bookmark.keyword) {
+ yield PlacesUtils.keywords.insert({
+ keyword: bookmark.keyword,
+ url: bookmark.url,
+ });
+ }
+
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: bookmark.url,
+ title: bookmark.title,
+ });
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ if (bookmark.keyword) {
+ yield PlacesUtils.keywords.remove(bookmark.keyword);
+ }
+ });
+}
+
+/**
+ * Check that if the user hits enter and ctrl-t at the same time, we open the URL in the right tab.
+ */
+add_task(function* hitEnterLoadInRightTab() {
+ info("Opening new tab");
+ let oldTabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ BrowserOpenTab();
+ let oldTab = (yield oldTabCreatedPromise).target;
+ let oldTabLoadedPromise = BrowserTestUtils.browserLoaded(oldTab.linkedBrowser, false, kURL);
+ oldTabLoadedPromise.then(() => info("Old tab loaded"));
+ let newTabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+
+ info("Creating bookmark and keyword");
+ yield addBookmark({title: "Test for keyword bookmark and URL", url: kURL, keyword: "urlbarkeyword"});
+ info("Filling URL bar, sending <return> and opening a tab");
+ gURLBar.value = "urlbarkeyword";
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ BrowserOpenTab();
+ info("Waiting for new tab");
+ let newTab = (yield newTabCreatedPromise).target;
+ info("Created new tab; waiting for either tab to load");
+ let newTabLoadedPromise = BrowserTestUtils.browserLoaded(newTab.linkedBrowser, false, kURL);
+ newTabLoadedPromise.then(() => info("New tab loaded"));
+ yield Promise.race([newTabLoadedPromise, oldTabLoadedPromise]);
+ is(newTab.linkedBrowser.currentURI.spec, "about:newtab", "New tab still has about:newtab");
+ is(oldTab.linkedBrowser.currentURI.spec, kURL, "Old tab loaded URL");
+ info("Closing new tab");
+ yield BrowserTestUtils.removeTab(newTab);
+ info("Closing old tab");
+ yield BrowserTestUtils.removeTab(oldTab);
+ info("Finished");
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarRevert.js b/browser/base/content/test/urlbar/browser_urlbarRevert.js
new file mode 100644
index 000000000..0ce3c8fac
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarRevert.js
@@ -0,0 +1,37 @@
+var tab = null;
+
+function test() {
+ waitForExplicitFinish();
+
+ let pageLoaded = {
+ onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ gBrowser.removeProgressListener(this);
+ executeSoon(checkURLBarRevert);
+ }
+ }
+ }
+
+ gBrowser.addProgressListener(pageLoaded);
+ tab = gBrowser.addTab("http://example.com");
+ gBrowser.selectedTab = tab;
+}
+
+function checkURLBarRevert() {
+ let originalValue = gURLBar.value;
+
+ gBrowser.userTypedValue = "foobar";
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ gBrowser.selectedTab = tab;
+ is(gURLBar.value, "foobar", "location bar displays typed value");
+
+ gURLBar.focus();
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+
+ is(gURLBar.value, originalValue, "ESC reverted the location bar value");
+
+ gBrowser.removeTab(tab);
+ finish();
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
new file mode 100644
index 000000000..ee0342055
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
@@ -0,0 +1,198 @@
+"use strict";
+
+var notificationObserver;
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost");
+ if (notificationObserver) {
+ notificationObserver.disconnect();
+ }
+});
+
+function promiseNotification(aBrowser, value, expected, input) {
+ let deferred = Promise.defer();
+ let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser);
+ if (expected) {
+ info("Waiting for " + value + " notification");
+ let checkForNotification = function() {
+ if (notificationBox.getNotificationWithValue(value)) {
+ info("Saw the notification");
+ notificationObserver.disconnect();
+ notificationObserver = null;
+ deferred.resolve();
+ }
+ }
+ if (notificationObserver) {
+ notificationObserver.disconnect();
+ }
+ notificationObserver = new MutationObserver(checkForNotification);
+ notificationObserver.observe(notificationBox, {childList: true});
+ } else {
+ setTimeout(() => {
+ is(notificationBox.getNotificationWithValue(value), null,
+ `We are expecting to not get a notification for ${input}`);
+ deferred.resolve();
+ }, 1000);
+ }
+ return deferred.promise;
+}
+
+function* runURLBarSearchTest({valueToOpen, expectSearch, expectNotification, aWindow=window}) {
+ aWindow.gURLBar.value = valueToOpen;
+ let expectedURI;
+ if (!expectSearch) {
+ expectedURI = "http://" + valueToOpen + "/";
+ } else {
+ yield new Promise(resolve => {
+ Services.search.init(resolve);
+ });
+ expectedURI = Services.search.defaultEngine.getSubmission(valueToOpen, null, "keyword").uri.spec;
+ }
+ aWindow.gURLBar.focus();
+ let docLoadPromise = waitForDocLoadAndStopIt(expectedURI, aWindow.gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {}, aWindow);
+
+ yield Promise.all([
+ docLoadPromise,
+ promiseNotification(aWindow.gBrowser, "keyword-uri-fixup", expectNotification, valueToOpen)
+ ]);
+}
+
+add_task(function* test_navigate_full_domain() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "www.mozilla.org",
+ expectSearch: false,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_decimal_ip() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "1234",
+ expectSearch: true,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_decimal_ip_with_path() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "1234/12",
+ expectSearch: true,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_large_number() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "123456789012345",
+ expectSearch: true,
+ expectNotification: false
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_small_hex_number() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "0x1f00ffff",
+ expectSearch: true,
+ expectNotification: false
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_large_hex_number() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "0x7f0000017f000001",
+ expectSearch: true,
+ expectNotification: false
+ });
+ gBrowser.removeTab(tab);
+});
+
+function get_test_function_for_localhost_with_hostname(hostName, isPrivate) {
+ return function* test_navigate_single_host() {
+ const pref = "browser.fixup.domainwhitelist.localhost";
+ let win;
+ if (isPrivate) {
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ win = OpenBrowserWindow({private: true});
+ yield promiseWin;
+ let deferredOpenFocus = Promise.defer();
+ waitForFocus(deferredOpenFocus.resolve, win);
+ yield deferredOpenFocus.promise;
+ } else {
+ win = window;
+ }
+ let browser = win.gBrowser;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(browser);
+
+ Services.prefs.setBoolPref(pref, false);
+ yield* runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: true,
+ expectNotification: true,
+ aWindow: win,
+ });
+
+ let notificationBox = browser.getNotificationBox(tab.linkedBrowser);
+ let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
+ let docLoadPromise = waitForDocLoadAndStopIt("http://" + hostName + "/", tab.linkedBrowser);
+ notification.querySelector(".notification-button-default").click();
+
+ // check pref value
+ let prefValue = Services.prefs.getBoolPref(pref);
+ is(prefValue, !isPrivate, "Pref should have the correct state.");
+
+ yield docLoadPromise;
+ browser.removeTab(tab);
+
+ // Now try again with the pref set.
+ tab = browser.selectedTab = browser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ // In a private window, the notification should appear again.
+ yield* runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: isPrivate,
+ expectNotification: isPrivate,
+ aWindow: win,
+ });
+ browser.removeTab(tab);
+ if (isPrivate) {
+ info("Waiting for private window to close");
+ yield BrowserTestUtils.closeWindow(win);
+ let deferredFocus = Promise.defer();
+ info("Waiting for focus");
+ waitForFocus(deferredFocus.resolve, window);
+ yield deferredFocus.promise;
+ }
+ }
+}
+
+add_task(get_test_function_for_localhost_with_hostname("localhost"));
+add_task(get_test_function_for_localhost_with_hostname("localhost."));
+add_task(get_test_function_for_localhost_with_hostname("localhost", true));
+
+add_task(function* test_navigate_invalid_url() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "mozilla is awesome",
+ expectSearch: true,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
new file mode 100644
index 000000000..5146ba98c
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
@@ -0,0 +1,66 @@
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ Services.search.currentEngine = oldCurrentEngine;
+
+ // Clicking suggestions causes visits to search results pages, so clear that
+ // history now.
+ yield PlacesTestUtils.clearHistory();
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ });
+});
+
+add_task(function* clickSuggestion() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ let [idx, suggestion, engineName] = yield promiseFirstSuggestion();
+ Assert.equal(engineName,
+ "browser_searchSuggestionEngine%20searchSuggestionEngine.xml",
+ "Expected suggestion engine");
+ let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
+
+ let uri = Services.search.currentEngine.getSubmission(suggestion).uri;
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
+ false, uri.spec);
+ item.click();
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+function getFirstSuggestion() {
+ let controller = gURLBar.popup.input.controller;
+ let matchCount = controller.matchCount;
+ for (let i = 0; i < matchCount; i++) {
+ let url = controller.getValueAt(i);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (mozActionMatch) {
+ let [, type, paramStr] = mozActionMatch;
+ let params = JSON.parse(paramStr);
+ if (type == "searchengine" && "searchSuggestion" in params) {
+ return [i, params.searchSuggestion, params.engineName];
+ }
+ }
+ }
+ return [-1, null, null];
+}
+
+function* promiseFirstSuggestion() {
+ let tuple = [-1, null, null];
+ yield BrowserTestUtils.waitForCondition(() => {
+ tuple = getFirstSuggestion();
+ return tuple[0] >= 0;
+ });
+ return tuple;
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
new file mode 100644
index 000000000..94ae8a3ff
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
@@ -0,0 +1,254 @@
+const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ registerCleanupFunction(function* () {
+ Services.search.currentEngine = oldCurrentEngine;
+ Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+
+ // Disable the notification for future tests so it doesn't interfere with
+ // them. clearUserPref() won't work because by default the pref is false.
+ yield setUserMadeChoicePref(true);
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ });
+});
+
+add_task(function* focus() {
+ // Focusing the urlbar used to open the popup in order to show the
+ // notification, but it doesn't anymore. Make sure it does not.
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+});
+
+add_task(function* dismissWithoutResults() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ let popupPromise = promisePopupShown(gURLBar.popup);
+ gURLBar.openPopup();
+ yield popupPromise;
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(true);
+ Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
+ let disableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+ );
+ let transitionPromise = promiseTransition();
+ disableButton.click();
+ yield transitionPromise;
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(false);
+});
+
+add_task(function* dismissWithResults() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(true);
+ Assert.ok(gURLBar.popup._matchCount > 0, "popup should have results");
+ let disableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+ );
+ let transitionPromise = promiseTransition();
+ disableButton.click();
+ yield transitionPromise;
+ Assert.ok(gURLBar.popup.popupOpen, "popup should remain open");
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(false);
+});
+
+add_task(function* disable() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(true);
+ let disableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+ );
+ let transitionPromise = promiseTransition();
+ disableButton.click();
+ yield transitionPromise;
+ gURLBar.blur();
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(!suggestionsPresent());
+});
+
+add_task(function* enable() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ assertVisible(true);
+ Assert.ok(!suggestionsPresent());
+ let enableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-enable"
+ );
+ let searchPromise = BrowserTestUtils.waitForCondition(suggestionsPresent,
+ "waiting for suggestions");
+ enableButton.click();
+ yield searchPromise;
+ // Clicking Yes should trigger a new search so that suggestions appear
+ // immediately.
+ Assert.ok(suggestionsPresent());
+ gURLBar.blur();
+ gURLBar.focus();
+ // Suggestions should still be present in a new search of course.
+ yield promiseAutocompleteResultPopup("bar");
+ Assert.ok(suggestionsPresent());
+});
+
+add_task(function* privateWindow() {
+ // Since suggestions are disabled in private windows, the notification should
+ // not appear even when suggestions are otherwise enabled.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ win.gURLBar.blur();
+ win.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo", win);
+ assertVisible(false, win);
+ win.gURLBar.blur();
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+add_task(function* multipleWindows() {
+ // Opening multiple windows, using their urlbars, and then dismissing the
+ // notification in one should dismiss the notification in all.
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+ yield setUserMadeChoicePref(false);
+
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win1");
+ assertVisible(true);
+
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+ win2.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win2", win2);
+ assertVisible(true, win2);
+
+ let win3 = yield BrowserTestUtils.openNewBrowserWindow();
+ win3.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win3", win3);
+ assertVisible(true, win3);
+
+ let enableButton = win3.document.getAnonymousElementByAttribute(
+ win3.gURLBar.popup, "anonid", "search-suggestions-notification-enable"
+ );
+ let transitionPromise = promiseTransition(win3);
+ enableButton.click();
+ yield transitionPromise;
+ assertVisible(false, win3);
+
+ win2.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win2done", win2);
+ assertVisible(false, win2);
+
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win1done");
+ assertVisible(false);
+
+ yield BrowserTestUtils.closeWindow(win2);
+ yield BrowserTestUtils.closeWindow(win3);
+});
+
+add_task(function* enableOutsideNotification() {
+ // Setting the suggest.searches pref outside the notification (e.g., by
+ // ticking the checkbox in the preferences window) should hide it.
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+ yield setUserMadeChoicePref(false);
+
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ assertVisible(false);
+});
+
+/**
+ * Setting the choice pref triggers a pref observer in the urlbar, which hides
+ * the notification if it's present. This function returns a promise that's
+ * resolved once the observer fires.
+ *
+ * @param userMadeChoice A boolean, the pref's new value.
+ * @return A Promise that's resolved when the observer fires -- or, if the pref
+ * is currently the given value, that's resolved immediately.
+ */
+function setUserMadeChoicePref(userMadeChoice) {
+ return new Promise(resolve => {
+ let currentUserMadeChoice = Services.prefs.getBoolPref(CHOICE_PREF);
+ if (currentUserMadeChoice != userMadeChoice) {
+ Services.prefs.addObserver(CHOICE_PREF, function obs(subj, topic, data) {
+ Services.prefs.removeObserver(CHOICE_PREF, obs);
+ resolve();
+ }, false);
+ }
+ Services.prefs.setBoolPref(CHOICE_PREF, userMadeChoice);
+ if (currentUserMadeChoice == userMadeChoice) {
+ resolve();
+ }
+ });
+}
+
+function suggestionsPresent() {
+ let controller = gURLBar.popup.input.controller;
+ let matchCount = controller.matchCount;
+ for (let i = 0; i < matchCount; i++) {
+ let url = controller.getValueAt(i);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (mozActionMatch) {
+ let [, type, paramStr] = mozActionMatch;
+ let params = JSON.parse(paramStr);
+ if (type == "searchengine" && "searchSuggestion" in params) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function assertVisible(visible, win=window) {
+ let style =
+ win.getComputedStyle(win.gURLBar.popup.searchSuggestionsNotification);
+ Assert.equal(style.visibility, visible ? "visible" : "collapse");
+}
+
+function promiseTransition(win=window) {
+ return new Promise(resolve => {
+ win.gURLBar.popup.addEventListener("transitionend", function onEnd() {
+ win.gURLBar.popup.removeEventListener("transitionend", onEnd, true);
+ // The urlbar needs to handle the transitionend first, but that happens
+ // naturally since promises are resolved at the end of the current tick.
+ resolve();
+ }, true);
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
new file mode 100644
index 000000000..8c28401ea
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
@@ -0,0 +1,216 @@
+"use strict";
+
+Cu.import("resource:///modules/BrowserUITelemetry.jsm");
+
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ Services.search.currentEngine = oldCurrentEngine;
+
+ // Clicking urlbar results causes visits to their associated pages, so clear
+ // that history now.
+ yield PlacesTestUtils.clearHistory();
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ });
+
+ // Move the mouse away from the urlbar one-offs so that a one-off engine is
+ // not inadvertently selected.
+ yield new Promise(resolve => {
+ EventUtils.synthesizeNativeMouseMove(window.document.documentElement, 0, 0,
+ resolve);
+ });
+});
+
+add_task(function* heuristicResultMouse() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("heuristicResult");
+ let action = getActionAtIndex(0);
+ Assert.ok(!!action, "there should be an action at index 0");
+ Assert.equal(action.type, "searchengine", "type should be searchengine");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.popup.richlistbox.getItemAtIndex(0).click();
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* heuristicResultKeyboard() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("heuristicResult");
+ let action = getActionAtIndex(0);
+ Assert.ok(!!action, "there should be an action at index 0");
+ Assert.equal(action.type, "searchengine", "type should be searchengine");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.sendKey("return");
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* searchSuggestionMouse() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("searchSuggestion");
+ let idx = getFirstSuggestionIndex();
+ Assert.ok(idx >= 0, "there should be a first suggestion");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.popup.richlistbox.getItemAtIndex(idx).click();
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* searchSuggestionKeyboard() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("searchSuggestion");
+ let idx = getFirstSuggestionIndex();
+ Assert.ok(idx >= 0, "there should be a first suggestion");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ while (idx--) {
+ EventUtils.sendKey("down");
+ }
+ EventUtils.sendKey("return");
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+/**
+ * This does three things: gets current telemetry/FHR counts, calls
+ * clickCallback, gets telemetry/FHR counts again to compare them to the old
+ * counts.
+ *
+ * @param clickCallback Use this to open the urlbar popup and choose and click a
+ * result.
+ */
+function* compareCounts(clickCallback) {
+ // Search events triggered by clicks (not the Return key in the urlbar) are
+ // recorded in three places:
+ // * BrowserUITelemetry
+ // * Telemetry histogram named "SEARCH_COUNTS"
+ // * FHR
+
+ let engine = Services.search.currentEngine;
+ let engineID = "org.mozilla.testsearchsuggestions";
+
+ // First, get the current counts.
+
+ // BrowserUITelemetry
+ let uiTelemCount = 0;
+ let bucket = BrowserUITelemetry.currentBucket;
+ let events = BrowserUITelemetry.getToolbarMeasures().countableEvents;
+ if (events[bucket] &&
+ events[bucket].search &&
+ events[bucket].search.urlbar) {
+ uiTelemCount = events[bucket].search.urlbar;
+ }
+
+ // telemetry histogram SEARCH_COUNTS
+ let histogramCount = 0;
+ let histogramKey = engineID + ".urlbar";
+ let histogram;
+ try {
+ histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ } catch (ex) {
+ // No searches performed yet, not a problem.
+ }
+ if (histogram) {
+ let snapshot = histogram.snapshot();
+ if (histogramKey in snapshot) {
+ histogramCount = snapshot[histogramKey].sum;
+ }
+ }
+
+ // FHR -- first make sure the engine has an identifier so that FHR is happy.
+ Object.defineProperty(engine.wrappedJSObject, "identifier",
+ { value: engineID });
+
+ gURLBar.focus();
+ yield clickCallback();
+
+ // Now get the new counts and compare them to the old.
+
+ // BrowserUITelemetry
+ events = BrowserUITelemetry.getToolbarMeasures().countableEvents;
+ Assert.ok(bucket in events, "bucket should be recorded");
+ events = events[bucket];
+ Assert.ok("search" in events, "search should be recorded");
+ events = events.search;
+ Assert.ok("urlbar" in events, "urlbar should be recorded");
+ Assert.equal(events.urlbar, uiTelemCount + 1,
+ "clicked suggestion should be recorded");
+
+ // telemetry histogram SEARCH_COUNTS
+ histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ let snapshot = histogram.snapshot();
+ Assert.ok(histogramKey in snapshot, "histogram with key should be recorded");
+ Assert.equal(snapshot[histogramKey].sum, histogramCount + 1,
+ "histogram sum should be incremented");
+}
+
+/**
+ * Returns the "action" object at the given index in the urlbar results:
+ * { type, params: {}}
+ *
+ * @param index The index in the urlbar results.
+ * @return An action object, or null if index >= number of results.
+ */
+function getActionAtIndex(index) {
+ let controller = gURLBar.popup.input.controller;
+ if (controller.matchCount <= index) {
+ return null;
+ }
+ let url = controller.getValueAt(index);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (!mozActionMatch) {
+ let msg = "result at index " + index + " is not a moz-action: " + url;
+ Assert.ok(false, msg);
+ throw new Error(msg);
+ }
+ let [, type, paramStr] = mozActionMatch;
+ return {
+ type: type,
+ params: JSON.parse(paramStr),
+ };
+}
+
+/**
+ * Returns the index of the first search suggestion in the urlbar results.
+ *
+ * @return An index, or -1 if there are no search suggestions.
+ */
+function getFirstSuggestionIndex() {
+ let controller = gURLBar.popup.input.controller;
+ let matchCount = controller.matchCount;
+ for (let i = 0; i < matchCount; i++) {
+ let url = controller.getValueAt(i);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (mozActionMatch) {
+ let [, type, paramStr] = mozActionMatch;
+ let params = JSON.parse(paramStr);
+ if (type == "searchengine" && "searchSuggestion" in params) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarStop.js b/browser/base/content/test/urlbar/browser_urlbarStop.js
new file mode 100644
index 000000000..8cf9d8017
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarStop.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const goodURL = "http://mochi.test:8888/";
+const badURL = "http://mochi.test:8888/whatever.html";
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab(goodURL);
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(gURLBar.textValue, gURLBar.trimValue(goodURL), "location bar reflects loaded page");
+
+ yield typeAndSubmitAndStop(badURL);
+ is(gURLBar.textValue, gURLBar.trimValue(goodURL), "location bar reflects loaded page after stop()");
+ gBrowser.removeCurrentTab();
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ is(gURLBar.textValue, "", "location bar is empty");
+
+ yield typeAndSubmitAndStop(badURL);
+ is(gURLBar.textValue, gURLBar.trimValue(badURL), "location bar reflects stopped page in an empty tab");
+ gBrowser.removeCurrentTab();
+});
+
+function* typeAndSubmitAndStop(url) {
+ yield promiseAutocompleteResultPopup(url, window, true);
+ is(gURLBar.textValue, gURLBar.trimValue(url), "location bar reflects loading page");
+
+ let promise = waitForDocLoadAndStopIt(url, gBrowser.selectedBrowser, false);
+ gURLBar.handleCommand();
+ yield promise;
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js b/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js
new file mode 100644
index 000000000..913e99a8e
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js
@@ -0,0 +1,98 @@
+add_task(function* () {
+ const PREF_TRIMURLS = "browser.urlbar.trimURLs";
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ registerCleanupFunction(function* () {
+ yield BrowserTestUtils.removeTab(tab);
+ Services.prefs.clearUserPref(PREF_TRIMURLS);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, true);
+
+ testVal("http://mozilla.org/", "mozilla.org");
+ testVal("https://mozilla.org/", "https://mozilla.org");
+ testVal("http://mözilla.org/", "mözilla.org");
+ testVal("http://mozilla.imaginatory/", "mozilla.imaginatory");
+ testVal("http://www.mozilla.org/", "www.mozilla.org");
+ testVal("http://sub.mozilla.org/", "sub.mozilla.org");
+ testVal("http://sub1.sub2.sub3.mozilla.org/", "sub1.sub2.sub3.mozilla.org");
+ testVal("http://mozilla.org/file.ext", "mozilla.org/file.ext");
+ testVal("http://mozilla.org/sub/", "mozilla.org/sub/");
+
+ testVal("http://ftp.mozilla.org/", "ftp.mozilla.org");
+ testVal("http://ftp1.mozilla.org/", "ftp1.mozilla.org");
+ testVal("http://ftp42.mozilla.org/", "ftp42.mozilla.org");
+ testVal("http://ftpx.mozilla.org/", "ftpx.mozilla.org");
+ testVal("ftp://ftp.mozilla.org/", "ftp://ftp.mozilla.org");
+ testVal("ftp://ftp1.mozilla.org/", "ftp://ftp1.mozilla.org");
+ testVal("ftp://ftp42.mozilla.org/", "ftp://ftp42.mozilla.org");
+ testVal("ftp://ftpx.mozilla.org/", "ftp://ftpx.mozilla.org");
+
+ testVal("https://user:pass@mozilla.org/", "https://user:pass@mozilla.org");
+ testVal("https://user@mozilla.org/", "https://user@mozilla.org");
+ testVal("http://user:pass@mozilla.org/", "user:pass@mozilla.org");
+ testVal("http://user@mozilla.org/", "user@mozilla.org");
+ testVal("http://sub.mozilla.org:666/", "sub.mozilla.org:666");
+
+ testVal("https://[fe80::222:19ff:fe11:8c76]/file.ext");
+ testVal("http://[fe80::222:19ff:fe11:8c76]/", "[fe80::222:19ff:fe11:8c76]");
+ testVal("https://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+ testVal("http://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext", "user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+
+ // Behaviour for hosts with no dots depends on the whitelist:
+ let fixupWhitelistPref = "browser.fixup.domainwhitelist.localhost";
+ Services.prefs.setBoolPref(fixupWhitelistPref, false);
+ testVal("http://localhost");
+ Services.prefs.setBoolPref(fixupWhitelistPref, true);
+ testVal("http://localhost", "localhost");
+ Services.prefs.clearUserPref(fixupWhitelistPref);
+
+ testVal("http:// invalid url");
+
+ testVal("http://someotherhostwithnodots");
+ testVal("http://localhost/ foo bar baz");
+ testVal("http://localhost.localdomain/ foo bar baz", "localhost.localdomain/ foo bar baz");
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, false);
+
+ testVal("http://mozilla.org/");
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, true);
+
+ let promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
+ false, "http://example.com/");
+ gBrowser.loadURI("http://example.com/");
+ yield promiseLoaded;
+
+ yield testCopy("example.com", "http://example.com/")
+
+ SetPageProxyState("invalid");
+ gURLBar.valueIsTyped = true;
+ yield testCopy("example.com", "example.com");
+});
+
+function testVal(originalValue, targetValue) {
+ gURLBar.value = originalValue;
+ gURLBar.valueIsTyped = false;
+ is(gURLBar.textValue, targetValue || originalValue, "url bar value set");
+}
+
+function testCopy(originalValue, targetValue) {
+ return new Promise((resolve, reject) => {
+ waitForClipboard(targetValue, function () {
+ is(gURLBar.textValue, originalValue, "url bar copy value set");
+
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ }, resolve, reject);
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js b/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js
new file mode 100644
index 000000000..c3cdf507f
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js
@@ -0,0 +1,17 @@
+"use strict";
+
+/**
+ * Disable keyword.enabled (so no keyword search), and check that when you type in
+ * "example" and hit enter, the browser loads and the URL bar is updated accordingly.
+ */
+add_task(function* () {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]}, resolve));
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ gURLBar.value = "example";
+ gURLBar.select();
+ let loadPromise = BrowserTestUtils.browserLoaded(browser, false, url => url == "http://www.example.com/");
+ EventUtils.sendKey("return");
+ yield loadPromise;
+ is(gURLBar.textValue, "www.example.com");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js b/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js
new file mode 100644
index 000000000..7fefd3f77
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js
@@ -0,0 +1,146 @@
+/* This test ensures that backspacing autoFilled values still allows to
+ * confirm the remaining value.
+ */
+
+function* test_autocomplete(data) {
+ let {desc, typed, autofilled, modified, keys, action, onAutoFill} = data;
+ info(desc);
+
+ yield promiseAutocompleteResultPopup(typed);
+ is(gURLBar.textValue, autofilled, "autofilled value is as expected");
+ if (onAutoFill)
+ onAutoFill()
+
+ keys.forEach(key => EventUtils.synthesizeKey(key, {}));
+
+ is(gURLBar.textValue, modified, "backspaced value is as expected");
+
+ yield promiseSearchComplete();
+
+ ok(gURLBar.popup.richlistbox.children.length > 0, "Should get at least 1 result");
+ let result = gURLBar.popup.richlistbox.children[0];
+ let type = result.getAttribute("type");
+ let types = type.split(/\s+/);
+ ok(types.indexOf(action) >= 0, `The type attribute "${type}" includes the expected action "${action}"`);
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ gURLBar.blur();
+}
+
+add_task(function* () {
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("browser.urlbar.autoFill");
+ gURLBar.handleRevert();
+ yield PlacesTestUtils.clearHistory();
+ });
+ Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
+
+ // Add a typed visit, so it will be autofilled.
+ yield PlacesTestUtils.addVisits({
+ uri: NetUtil.newURI("http://example.com/"),
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED
+ });
+
+ yield test_autocomplete({ desc: "DELETE the autofilled part should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exam",
+ keys: ["VK_DELETE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "DELETE the final slash should visit",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["VK_DELETE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "BACK_SPACE the autofilled part should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exam",
+ keys: ["VK_BACK_SPACE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "BACK_SPACE the final slash should visit",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["VK_BACK_SPACE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "DELETE the autofilled part, then BACK_SPACE, should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exa",
+ keys: ["VK_DELETE", "VK_BACK_SPACE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "DELETE the final slash, then BACK_SPACE, should search",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.co",
+ keys: ["VK_DELETE", "VK_BACK_SPACE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "BACK_SPACE the autofilled part, then BACK_SPACE, should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exa",
+ keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "BACK_SPACE the final slash, then BACK_SPACE, should search",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.co",
+ keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "BACK_SPACE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["VK_BACK_SPACE"],
+ action: "searchengine",
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ gURLBar.selectionStart = 1;
+ gURLBar.selectionEnd = 12;
+ }
+ });
+ yield test_autocomplete({ desc: "DELETE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["VK_DELETE"],
+ action: "searchengine",
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ gURLBar.selectionStart = 1;
+ gURLBar.selectionEnd = 12;
+ }
+ });
+ yield test_autocomplete({ desc: "double BACK_SPACE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"],
+ action: "searchengine",
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ gURLBar.selectionStart = 2;
+ gURLBar.selectionEnd = 12;
+ }
+ });
+
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_blanking.js b/browser/base/content/test/urlbar/browser_urlbar_blanking.js
new file mode 100644
index 000000000..13660edab
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_blanking.js
@@ -0,0 +1,35 @@
+"use strict";
+
+add_task(function*() {
+ for (let page of gInitialPages) {
+ if (page == "about:newtab") {
+ // New tab preloading makes this a pain to test, so skip
+ continue;
+ }
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, page);
+ ok(!gURLBar.value, "The URL bar should be empty if we load a plain " + page + " page.");
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(function*() {
+ const URI = "http://www.example.com/browser/browser/base/content/test/urlbar/file_blank_but_not_blank.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URI);
+ is(gURLBar.value, URI, "The URL bar should match the URI");
+ let browserLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ ContentTask.spawn(tab.linkedBrowser, null, function() {
+ content.document.querySelector('a').click();
+ });
+ yield browserLoaded;
+ ok(gURLBar.value.startsWith("javascript"), "The URL bar should have the JS URI");
+ // When reloading, the javascript: uri we're using will throw an exception.
+ // That's deliberate, so we need to tell mochitest to ignore it:
+ SimpleTest.expectUncaughtException(true);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ // This is sync, so by the time we return we should have changed the URL bar.
+ content.location.reload();
+ });
+ ok(!!gURLBar.value, "URL bar should not be blank.");
+ yield BrowserTestUtils.removeTab(tab);
+ SimpleTest.expectUncaughtException(false);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js b/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js
new file mode 100644
index 000000000..63ed58a62
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js
@@ -0,0 +1,41 @@
+"use strict";
+
+function* checkURLBarValueStays(browser) {
+ gURLBar.select();
+ EventUtils.synthesizeKey("a", {});
+ is(gURLBar.value, "a", "URL bar value should match after sending a key");
+ yield new Promise(resolve => {
+ let listener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ ok(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
+ "Should only get a same document location change");
+ gBrowser.selectedBrowser.removeProgressListener(filter);
+ filter = null;
+ resolve();
+ },
+ };
+ let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+ gBrowser.selectedBrowser.addProgressListener(filter);
+ });
+ is(gURLBar.value, "a", "URL bar should not have been changed by location changes.");
+}
+
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com/browser/browser/base/content/test/urlbar/file_urlbar_edit_dos.html"
+ }, function*(browser) {
+ yield ContentTask.spawn(browser, "", function() {
+ content.wrappedJSObject.dos_hash();
+ });
+ yield checkURLBarValueStays(browser);
+ yield ContentTask.spawn(browser, "", function() {
+ content.clearTimeout(content.wrappedJSObject.dos_timeout);
+ content.wrappedJSObject.dos_pushState();
+ });
+ yield checkURLBarValueStays(browser);
+ });
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js
new file mode 100644
index 000000000..9a1df0505
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js
@@ -0,0 +1,39 @@
+"use strict";
+
+/**
+ * Verify that when loading and going back/forward through history between URLs
+ * loaded in the content process, and URLs loaded in the parent process, we
+ * don't set the URL for the tab to about:blank inbetween the loads.
+ */
+add_task(function*() {
+ let url = "http://www.example.com/foo.html";
+ yield BrowserTestUtils.withNewTab({gBrowser, url}, function*(browser) {
+ let wpl = {
+ onLocationChange(wpl, request, location, flags) {
+ if (location.schemeIs("about")) {
+ is(location.spec, "about:config", "Only about: location change should be for about:preferences");
+ } else {
+ is(location.spec, url, "Only non-about: location change should be for the http URL we're dealing with.");
+ }
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ let didLoad = BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+ return loadedURL == "about:config";
+ });
+ yield BrowserTestUtils.loadURI(browser, "about:config");
+ yield didLoad;
+
+ gBrowser.goBack();
+ yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+ return url == loadedURL;
+ });
+ gBrowser.goForward();
+ yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+ return loadedURL == "about:config";
+ });
+ gBrowser.removeProgressListener(wpl);
+ });
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js b/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js
new file mode 100644
index 000000000..04b1c508b
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function*() {
+ let button = document.getElementById("urlbar-search-settings");
+ if (!button) {
+ ok("Skipping test");
+ return;
+ }
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* () {
+ let popupopened = BrowserTestUtils.waitForEvent(gURLBar.popup, "popupshown");
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("a", {});
+ yield popupopened;
+
+ // Since the current tab is blank the preferences pane will load there
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ let popupclosed = BrowserTestUtils.waitForEvent(gURLBar.popup, "popuphidden");
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield loaded;
+ yield popupclosed;
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "about:preferences#search",
+ "Should have loaded the right page");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
new file mode 100644
index 000000000..6b6a10ea3
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
@@ -0,0 +1,138 @@
+"use strict";
+
+const SLOW_PAGE = "http://www.example.com/browser/browser/base/content/test/urlbar/slow-page.sjs";
+const SLOW_PAGE2 = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/slow-page.sjs?faster";
+
+/**
+ * Check that if we:
+ * 1) have a loaded page
+ * 2) load a separate URL
+ * 3) before the URL for step 2 has finished loading, load a third URL
+ * we don't revert to the URL from (1).
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com", true, true);
+
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let handler = e => {
+ sawChange = true;
+ is(gURLBar.value, expectedURLBarChange, "Should not change URL bar value!");
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar, {attributes: true});
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // If this ever starts going intermittent, we've broken this.
+ yield new Promise(resolve => setTimeout(resolve, 200));
+ expectedURLBarChange = SLOW_PAGE2;
+ let pageLoadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.value = expectedURLBarChange;
+ gURLBar.handleCommand();
+ is(gURLBar.value, expectedURLBarChange, "Should not have changed URL bar value synchronously.");
+ yield pageLoadPromise;
+ ok(sawChange, "The URL bar change handler should have been called by the time the page was loaded");
+ obs.disconnect();
+ obs = null;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we switch to that tab and stop the request
+ *
+ * The URL bar continues to contain the URL of the page we wanted to visit.
+ */
+add_task(function*() {
+ let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+ socket.init(-1, true, -1);
+ const PORT = socket.port;
+ registerCleanupFunction(() => { socket.close(); });
+
+ const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+ const BASE_PAGE = TEST_PATH + "dummy_page.html";
+ const SLOW_HOST = `https://localhost:${PORT}/`;
+ info("Using URLs: " + SLOW_HOST);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+ info("opened tab");
+ yield ContentTask.spawn(tab.linkedBrowser, SLOW_HOST, URL => {
+ let link = content.document.createElement("a");
+ link.href = URL;
+ link.textContent = "click me to open a slow page";
+ link.id = "clickme"
+ content.document.body.appendChild(link);
+ });
+ info("added link");
+ let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ // Middle click the link:
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser);
+ // get new tab, switch to it
+ let newTab = (yield newTabPromise).target;
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gURLBar.value, SLOW_HOST, "Should have slow page in URL bar");
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+ BrowserStop();
+ yield browserStoppedPromise;
+
+ is(gURLBar.value, SLOW_HOST, "Should still have slow page in URL bar after stop");
+ yield BrowserTestUtils.removeTab(newTab);
+ yield BrowserTestUtils.removeTab(tab);
+});
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we alter the URL on that page to some other server that doesn't respond
+ * 3) we stop the request
+ *
+ * The URL bar continues to contain the second URL.
+ */
+add_task(function*() {
+ let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+ socket.init(-1, true, -1);
+ const PORT1 = socket.port;
+ let socket2 = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+ socket2.init(-1, true, -1);
+ const PORT2 = socket2.port;
+ registerCleanupFunction(() => { socket.close(); socket2.close(); });
+
+ const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+ const BASE_PAGE = TEST_PATH + "dummy_page.html";
+ const SLOW_HOST1 = `https://localhost:${PORT1}/`;
+ const SLOW_HOST2 = `https://localhost:${PORT2}/`;
+ info("Using URLs: " + SLOW_HOST1 + " and " + SLOW_HOST2);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+ info("opened tab");
+ yield ContentTask.spawn(tab.linkedBrowser, SLOW_HOST1, URL => {
+ let link = content.document.createElement("a");
+ link.href = URL;
+ link.textContent = "click me to open a slow page";
+ link.id = "clickme"
+ content.document.body.appendChild(link);
+ });
+ info("added link");
+ let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ // Middle click the link:
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser);
+ // get new tab, switch to it
+ let newTab = (yield newTabPromise).target;
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gURLBar.value, SLOW_HOST1, "Should have slow page in URL bar");
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+ gURLBar.value = SLOW_HOST2;
+ gURLBar.handleCommand();
+ yield browserStoppedPromise;
+
+ is(gURLBar.value, SLOW_HOST2, "Should have second slow page in URL bar");
+ browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+ BrowserStop();
+ yield browserStoppedPromise;
+
+ is(gURLBar.value, SLOW_HOST2, "Should still have second slow page in URL bar after stop");
+ yield BrowserTestUtils.removeTab(newTab);
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js
new file mode 100644
index 000000000..54b174aa8
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function testURLBarCopy(targetValue) {
+ return new Promise((resolve, reject) => {
+ info("Expecting copy of: " + targetValue);
+ waitForClipboard(targetValue, function () {
+ gURLBar.focus();
+ gURLBar.select();
+
+ goDoCommand("cmd_copy");
+ }, resolve, () => {
+ ok(false, "Clipboard copy failed");
+ reject();
+ });
+ });
+}
+
+add_task(function* () {
+ const url = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/test_wyciwyg_copying.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#btn", {}, tab.linkedBrowser);
+ let currentURL = gBrowser.currentURI.spec;
+ ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
+
+ yield testURLBarCopy(url);
+
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/urlbar/dummy_page.html b/browser/base/content/test/urlbar/dummy_page.html
new file mode 100644
index 000000000..1a87e2840
--- /dev/null
+++ b/browser/base/content/test/urlbar/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/urlbar/file_blank_but_not_blank.html b/browser/base/content/test/urlbar/file_blank_but_not_blank.html
new file mode 100644
index 000000000..1f5fea8dc
--- /dev/null
+++ b/browser/base/content/test/urlbar/file_blank_but_not_blank.html
@@ -0,0 +1,2 @@
+<script>var q = "1";</script>
+<a href="javascript:q">Click me</a>
diff --git a/browser/base/content/test/urlbar/file_urlbar_edit_dos.html b/browser/base/content/test/urlbar/file_urlbar_edit_dos.html
new file mode 100644
index 000000000..5a6e7d109
--- /dev/null
+++ b/browser/base/content/test/urlbar/file_urlbar_edit_dos.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Try editing the URL bar</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<script>
+var dos_timeout = null;
+function dos_hash() {
+ dos_timeout = setTimeout(function() {
+ location.hash = "#";
+ }, 50);
+}
+
+function dos_pushState() {
+ dos_timeout = setTimeout(function() {
+ history.pushState({}, "Some title", "");
+ }, 50);
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/urlbar/head.js b/browser/base/content/test/urlbar/head.js
new file mode 100644
index 000000000..427dba080
--- /dev/null
+++ b/browser/base/content/test/urlbar/head.js
@@ -0,0 +1,205 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+
+/**
+ * Waits for the next top-level document load in the current browser. The URI
+ * of the document is compared against aExpectedURL. The load is then stopped
+ * before it actually starts.
+ *
+ * @param aExpectedURL
+ * The URL of the document that is expected to load.
+ * @param aStopFromProgressListener
+ * Whether to cancel the load directly from the progress listener. Defaults to true.
+ * If you're using this method to avoid hitting the network, you want the default (true).
+ * However, the browser UI will behave differently for loads stopped directly from
+ * the progress listener (effectively in the middle of a call to loadURI) and so there
+ * are cases where you may want to avoid stopping the load directly from within the
+ * progress listener callback.
+ * @return promise
+ */
+function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) {
+ function content_script(aStopFromProgressListener) {
+ let { interfaces: Ci, utils: Cu } = Components;
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ let wp = docShell.QueryInterface(Ci.nsIWebProgress);
+
+ function stopContent(now, uri) {
+ if (now) {
+ /* Hammer time. */
+ content.stop();
+
+ /* Let the parent know we're done. */
+ sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri });
+ } else {
+ setTimeout(stopContent.bind(null, true, uri), 0);
+ }
+ }
+
+ let progressListener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
+
+ if (webProgress.isTopLevel &&
+ flags & Ci.nsIWebProgressListener.STATE_START) {
+ wp.removeProgressListener(progressListener);
+
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
+
+ stopContent(aStopFromProgressListener, chan.originalURI.spec);
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
+ };
+ wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
+
+ /**
+ * As |this| is undefined and we can't extend |docShell|, adding an unload
+ * event handler is the easiest way to ensure the weakly referenced
+ * progress listener is kept alive as long as necessary.
+ */
+ addEventListener("unload", function () {
+ try {
+ wp.removeProgressListener(progressListener);
+ } catch (e) { /* Will most likely fail. */ }
+ });
+ }
+
+ return new Promise((resolve, reject) => {
+ function complete({ data }) {
+ is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded");
+ mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ resolve();
+ }
+
+ let mm = aBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
+ mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
+ });
+}
+
+function is_hidden(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ if (style.display == "-moz-popup")
+ return ["hiding", "closed"].indexOf(element.state) != -1;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_hidden(element.parentNode);
+
+ return false;
+}
+
+function is_visible(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return false;
+ if (style.visibility != "visible")
+ return false;
+ if (style.display == "-moz-popup" && element.state != "open")
+ return false;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_visible(element.parentNode);
+
+ return true;
+}
+
+function is_element_visible(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_visible(element), msg || "Element should be visible");
+}
+
+function is_element_hidden(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(element), msg || "Element should be hidden");
+}
+
+function promisePopupEvent(popup, eventSuffix) {
+ let endState = {shown: "open", hidden: "closed"}[eventSuffix];
+
+ if (popup.state == endState)
+ return Promise.resolve();
+
+ let eventType = "popup" + eventSuffix;
+ let deferred = Promise.defer();
+ popup.addEventListener(eventType, function onPopupShown(event) {
+ popup.removeEventListener(eventType, onPopupShown);
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function promisePopupShown(popup) {
+ return promisePopupEvent(popup, "shown");
+}
+
+function promisePopupHidden(popup) {
+ return promisePopupEvent(popup, "hidden");
+}
+
+function promiseSearchComplete(win = window) {
+ return promisePopupShown(win.gURLBar.popup).then(() => {
+ function searchIsComplete() {
+ return win.gURLBar.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+ }
+
+ // Wait until there are at least two matches.
+ return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete");
+ });
+}
+
+function promiseAutocompleteResultPopup(inputText,
+ win = window,
+ fireInputEvent = false) {
+ waitForFocus(() => {
+ win.gURLBar.focus();
+ win.gURLBar.value = inputText;
+ if (fireInputEvent) {
+ // This is necessary to get the urlbar to set gBrowser.userTypedValue.
+ let event = document.createEvent("Events");
+ event.initEvent("input", true, true);
+ win.gURLBar.dispatchEvent(event);
+ }
+ win.gURLBar.controller.startSearch(inputText);
+ }, win);
+
+ return promiseSearchComplete(win);
+}
+
+function promiseNewSearchEngine(basename) {
+ return new Promise((resolve, reject) => {
+ info("Waiting for engine to be added: " + basename);
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ registerCleanupFunction(() => Services.search.removeEngine(engine));
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ Assert.ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+}
+
diff --git a/browser/base/content/test/urlbar/moz.png b/browser/base/content/test/urlbar/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/urlbar/moz.png
Binary files differ
diff --git a/browser/base/content/test/urlbar/print_postdata.sjs b/browser/base/content/test/urlbar/print_postdata.sjs
new file mode 100644
index 000000000..4175a2480
--- /dev/null
+++ b/browser/base/content/test/urlbar/print_postdata.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/base/content/test/urlbar/redirect_bug623155.sjs b/browser/base/content/test/urlbar/redirect_bug623155.sjs
new file mode 100644
index 000000000..64c6f143b
--- /dev/null
+++ b/browser/base/content/test/urlbar/redirect_bug623155.sjs
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status
+ aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently");
+
+ // Set redirect URI, mirroring the hash value.
+ let hash = (/\#.+/.test(aRequest.path))?
+ "#" + aRequest.path.split("#")[1]:
+ "";
+ aResponse.setHeader("Location", REDIRECT_TO + hash);
+}
diff --git a/browser/base/content/test/urlbar/searchSuggestionEngine.sjs b/browser/base/content/test/urlbar/searchSuggestionEngine.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/base/content/test/urlbar/searchSuggestionEngine.xml b/browser/base/content/test/urlbar/searchSuggestionEngine.xml
new file mode 100644
index 000000000..a5659792e
--- /dev/null
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/urlbar/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/urlbar/slow-page.sjs b/browser/base/content/test/urlbar/slow-page.sjs
new file mode 100644
index 000000000..f428d66e4
--- /dev/null
+++ b/browser/base/content/test/urlbar/slow-page.sjs
@@ -0,0 +1,22 @@
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let timer;
+
+const DELAY_MS = 5000;
+function handleRequest(request, response) {
+ if (request.queryString.endsWith("faster")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>Not so slow!</body>");
+ return;
+ }
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(() => {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>This was the slow load. You should never see this.</body>");
+ response.finish();
+ }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/browser/base/content/test/urlbar/test_wyciwyg_copying.html b/browser/base/content/test/urlbar/test_wyciwyg_copying.html
new file mode 100644
index 000000000..3a8c3a150
--- /dev/null
+++ b/browser/base/content/test/urlbar/test_wyciwyg_copying.html
@@ -0,0 +1,13 @@
+<html>
+<body>
+<script>
+ function go() {
+ var w = window.open();
+ w.document.open();
+ w.document.write("<html><body>test document</body></html>");
+ w.document.close();
+ }
+</script>
+<button id="btn" onclick="go();">test</button>
+</body>
+</html>
diff --git a/browser/base/content/test/webrtc/.eslintrc.js b/browser/base/content/test/webrtc/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/webrtc/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/webrtc/browser.ini b/browser/base/content/test/webrtc/browser.ini
new file mode 100644
index 000000000..8830989ad
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ get_user_media.html
+ get_user_media_content_script.js
+ head.js
+
+[browser_devices_get_user_media.js]
+skip-if = (os == "linux" && debug) # linux: bug 976544
+[browser_devices_get_user_media_anim.js]
+[browser_devices_get_user_media_in_frame.js]
+[browser_devices_get_user_media_tear_off_tab.js]
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media.js b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
new file mode 100644
index 000000000..3681a810b
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -0,0 +1,554 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+ "by the user agent or the platform in the current context.";
+
+var gTests = [
+
+{
+ desc: "getUserMedia audio+video",
+ run: function* checkAudioVideo() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareDevices-notification-icon", "anchored to device icon");
+ checkDeviceSelectors(true, true);
+ let iconclass =
+ PopupNotifications.panel.firstChild.getAttribute("iconclass");
+ ok(iconclass.includes("camera-icon"), "panel using devices icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({audio: true, video: true});
+ yield closeStream();
+ }
+},
+
+{
+ desc: "getUserMedia audio only",
+ run: function* checkAudioOnly() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareMicrophone-notification-icon", "anchored to mic icon");
+ checkDeviceSelectors(true);
+ let iconclass =
+ PopupNotifications.panel.firstChild.getAttribute("iconclass");
+ ok(iconclass.includes("microphone-icon"), "panel using microphone icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Microphone",
+ "expected microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({audio: true});
+ yield closeStream();
+ }
+},
+
+{
+ desc: "getUserMedia video only",
+ run: function* checkVideoOnly() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(false, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareDevices-notification-icon", "anchored to device icon");
+ checkDeviceSelectors(false, true);
+ let iconclass =
+ PopupNotifications.panel.firstChild.getAttribute("iconclass");
+ ok(iconclass.includes("camera-icon"), "panel using devices icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Camera", "expected camera to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true});
+ yield closeStream();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video, user clicks \"Don't Share\"",
+ run: function* checkDontShare() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ yield promiseMessage(permissionError, () => {
+ activateSecondaryAction(kActionDeny);
+ });
+
+ yield expectObserverCalled("getUserMedia:response:deny");
+ yield expectObserverCalled("recording-window-ended");
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: stop sharing",
+ run: function* checkStopSharing() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ yield stopSharing();
+
+ // the stream is already closed, but this will do some cleanup anyway
+ yield closeStream(true);
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the page removes all gUM UI",
+ run: function* checkReloading() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("reloading the web page");
+ promise = promiseObserverCalled("recording-device-events");
+ content.location.reload();
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia prompt: Always/Never Share",
+ run: function* checkRememberCheckbox() {
+ let elt = id => document.getElementById(id);
+
+ function* checkPerm(aRequestAudio, aRequestVideo,
+ aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(elt("webRTC-selectMicrophone").hidden, !aRequestAudio,
+ "microphone selector expected to be " + (aRequestAudio ? "visible" : "hidden"));
+
+ is(elt("webRTC-selectCamera").hidden, !aRequestVideo,
+ "camera selector expected to be " + (aRequestVideo ? "visible" : "hidden"));
+
+ let expectedMessage = aNever ? permissionError : "ok";
+ yield promiseMessage(expectedMessage, () => {
+ activateSecondaryAction(aNever ? kActionNever : kActionAlways);
+ });
+ let expected = [];
+ if (expectedMessage == "ok") {
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ if (aRequestVideo)
+ expected.push("Camera");
+ if (aRequestAudio)
+ expected.push("Microphone");
+ expected = expected.join("And");
+ }
+ else {
+ yield expectObserverCalled("getUserMedia:response:deny");
+ yield expectObserverCalled("recording-window-ended");
+ expected = "none";
+ }
+ is((yield getMediaCaptureState()), expected,
+ "expected " + expected + " to be shared");
+
+ function checkDevicePermissions(aDevice, aExpected) {
+ let Perms = Services.perms;
+ let uri = gBrowser.selectedBrowser.documentURI;
+ let devicePerms = Perms.testExactPermission(uri, aDevice);
+ if (aExpected === undefined)
+ is(devicePerms, Perms.UNKNOWN_ACTION, "no " + aDevice + " persistent permissions");
+ else {
+ is(devicePerms, aExpected ? Perms.ALLOW_ACTION : Perms.DENY_ACTION,
+ aDevice + " persistently " + (aExpected ? "allowed" : "denied"));
+ }
+ Perms.remove(uri, aDevice);
+ }
+ checkDevicePermissions("microphone", aExpectedAudioPerm);
+ checkDevicePermissions("camera", aExpectedVideoPerm);
+
+ if (expectedMessage == "ok")
+ yield closeStream();
+ }
+
+ // 3 cases where the user accepts the device prompt.
+ info("audio+video, user grants, expect both perms set to allow");
+ yield checkPerm(true, true, true, true);
+ info("audio only, user grants, check audio perm set to allow, video perm not set");
+ yield checkPerm(true, false, true, undefined);
+ info("video only, user grants, check video perm set to allow, audio perm not set");
+ yield checkPerm(false, true, undefined, true);
+
+ // 3 cases where the user rejects the device request by using 'Never Share'.
+ info("audio only, user denies, expect audio perm set to deny, video not set");
+ yield checkPerm(true, false, false, undefined, true);
+ info("video only, user denies, expect video perm set to deny, audio perm not set");
+ yield checkPerm(false, true, undefined, false, true);
+ info("audio+video, user denies, expect both perms set to deny");
+ yield checkPerm(true, true, false, false, true);
+ }
+},
+
+{
+ desc: "getUserMedia without prompt: use persistent permissions",
+ run: function* checkUsePersistentPermissions() {
+ function* usePerm(aAllowAudio, aAllowVideo, aRequestAudio, aRequestVideo,
+ aExpectStream) {
+ let Perms = Services.perms;
+ let uri = gBrowser.selectedBrowser.documentURI;
+
+ if (aAllowAudio !== undefined) {
+ Perms.add(uri, "microphone", aAllowAudio ? Perms.ALLOW_ACTION
+ : Perms.DENY_ACTION);
+ }
+ if (aAllowVideo !== undefined) {
+ Perms.add(uri, "camera", aAllowVideo ? Perms.ALLOW_ACTION
+ : Perms.DENY_ACTION);
+ }
+
+ if (aExpectStream === undefined) {
+ // Check that we get a prompt.
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ // Deny the request to cleanup...
+ yield promiseMessage(permissionError, () => {
+ activateSecondaryAction(kActionDeny);
+ });
+ yield expectObserverCalled("getUserMedia:response:deny");
+ yield expectObserverCalled("recording-window-ended");
+ }
+ else {
+ let expectedMessage = aExpectStream ? "ok" : permissionError;
+ let promise = promiseMessage(expectedMessage);
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+
+ if (expectedMessage == "ok") {
+ yield expectObserverCalled("getUserMedia:request");
+ yield promiseNoPopupNotification("webRTC-shareDevices");
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+
+ // Check what's actually shared.
+ let expected = [];
+ if (aAllowVideo && aRequestVideo)
+ expected.push("Camera");
+ if (aAllowAudio && aRequestAudio)
+ expected.push("Microphone");
+ expected = expected.join("And");
+ is((yield getMediaCaptureState()), expected,
+ "expected " + expected + " to be shared");
+
+ yield closeStream();
+ }
+ else {
+ yield expectObserverCalled("recording-window-ended");
+ }
+ }
+
+ Perms.remove(uri, "camera");
+ Perms.remove(uri, "microphone");
+ }
+
+ // Set both permissions identically
+ info("allow audio+video, request audio+video, expect ok (audio+video)");
+ yield usePerm(true, true, true, true, true);
+ info("deny audio+video, request audio+video, expect denied");
+ yield usePerm(false, false, true, true, false);
+
+ // Allow audio, deny video.
+ info("allow audio, deny video, request audio+video, expect denied");
+ yield usePerm(true, false, true, true, false);
+ info("allow audio, deny video, request audio, expect ok (audio)");
+ yield usePerm(true, false, true, false, true);
+ info("allow audio, deny video, request video, expect denied");
+ yield usePerm(true, false, false, true, false);
+
+ // Deny audio, allow video.
+ info("deny audio, allow video, request audio+video, expect denied");
+ yield usePerm(false, true, true, true, false);
+ info("deny audio, allow video, request audio, expect denied");
+ yield usePerm(false, true, true, false, false);
+ info("deny audio, allow video, request video, expect ok (video)");
+ yield usePerm(false, true, false, true, true);
+
+ // Allow audio, video not set.
+ info("allow audio, request audio+video, expect prompt");
+ yield usePerm(true, undefined, true, true, undefined);
+ info("allow audio, request audio, expect ok (audio)");
+ yield usePerm(true, undefined, true, false, true);
+ info("allow audio, request video, expect prompt");
+ yield usePerm(true, undefined, false, true, undefined);
+
+ // Deny audio, video not set.
+ info("deny audio, request audio+video, expect denied");
+ yield usePerm(false, undefined, true, true, false);
+ info("deny audio, request audio, expect denied");
+ yield usePerm(false, undefined, true, false, false);
+ info("deny audio, request video, expect prompt");
+ yield usePerm(false, undefined, false, true, undefined);
+
+ // Allow video, audio not set.
+ info("allow video, request audio+video, expect prompt");
+ yield usePerm(undefined, true, true, true, undefined);
+ info("allow video, request audio, expect prompt");
+ yield usePerm(undefined, true, true, false, undefined);
+ info("allow video, request video, expect ok (video)");
+ yield usePerm(undefined, true, false, true, true);
+
+ // Deny video, audio not set.
+ info("deny video, request audio+video, expect denied");
+ yield usePerm(undefined, false, true, true, false);
+ info("deny video, request audio, expect prompt");
+ yield usePerm(undefined, false, true, false, undefined);
+ info("deny video, request video, expect denied");
+ yield usePerm(undefined, false, false, true, false);
+ }
+},
+
+{
+ desc: "Stop Sharing removes persistent permissions",
+ run: function* checkStopSharingRemovesPersistentPermissions() {
+ function* stopAndCheckPerm(aRequestAudio, aRequestVideo) {
+ let Perms = Services.perms;
+ let uri = gBrowser.selectedBrowser.documentURI;
+
+ // Initially set both permissions to 'allow'.
+ Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
+ Perms.add(uri, "camera", Perms.ALLOW_ACTION);
+
+ let indicator = promiseIndicatorWindow();
+ // Start sharing what's been requested.
+ let promise = promiseMessage("ok");
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+
+ yield expectObserverCalled("getUserMedia:request");
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ yield indicator;
+ yield checkSharingUI({video: aRequestVideo, audio: aRequestAudio});
+
+ yield stopSharing(aRequestVideo ? "camera" : "microphone");
+
+ // Check that permissions have been removed as expected.
+ let audioPerm = Perms.testExactPermission(uri, "microphone");
+ if (aRequestAudio)
+ is(audioPerm, Perms.UNKNOWN_ACTION, "microphone permissions removed");
+ else
+ is(audioPerm, Perms.ALLOW_ACTION, "microphone permissions untouched");
+
+ let videoPerm = Perms.testExactPermission(uri, "camera");
+ if (aRequestVideo)
+ is(videoPerm, Perms.UNKNOWN_ACTION, "camera permissions removed");
+ else
+ is(videoPerm, Perms.ALLOW_ACTION, "camera permissions untouched");
+
+ // Cleanup.
+ yield closeStream(true);
+
+ Perms.remove(uri, "camera");
+ Perms.remove(uri, "microphone");
+ }
+
+ info("request audio+video, stop sharing resets both");
+ yield stopAndCheckPerm(true, true);
+ info("request audio, stop sharing resets audio only");
+ yield stopAndCheckPerm(true, false);
+ info("request video, stop sharing resets video only");
+ yield stopAndCheckPerm(false, true);
+ }
+},
+
+{
+ desc: "test showControlCenter",
+ run: function* checkShowControlCenter() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(false, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(false, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Camera", "expected camera to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true});
+
+ ok(gIdentityHandler._identityPopup.hidden, "control center should be hidden");
+ if ("nsISystemStatusBar" in Ci) {
+ let activeStreams = webrtcUI.getActiveStreams(true, false, false);
+ webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices");
+ }
+ else {
+ let win =
+ Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
+ let elt = win.document.getElementById("audioVideoButton");
+ EventUtils.synthesizeMouseAtCenter(elt, {}, win);
+ yield promiseWaitForCondition(() => !gIdentityHandler._identityPopup.hidden);
+ }
+ ok(!gIdentityHandler._identityPopup.hidden, "control center should be open");
+
+ gIdentityHandler._identityPopup.hidden = true;
+ yield expectNoObserverCalled();
+
+ yield closeStream();
+ }
+},
+
+{
+ desc: "'Always Allow' ignored and not shown on http pages",
+ run: function* checkNoAlwaysOnHttp() {
+ // Load an http page instead of the https version.
+ let browser = gBrowser.selectedBrowser;
+ browser.loadURI(browser.documentURI.spec.replace("https://", "http://"));
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // Initially set both permissions to 'allow'.
+ let Perms = Services.perms;
+ let uri = browser.documentURI;
+ Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
+ Perms.add(uri, "camera", Perms.ALLOW_ACTION);
+
+ // Request devices and expect a prompt despite the saved 'Allow' permission,
+ // because the connection isn't secure.
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ // Ensure that the 'Always Allow' action isn't shown.
+ let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label");
+ ok(!!alwaysLabel, "found the 'Always Allow' localized label");
+ let labels = [];
+ let notification = PopupNotifications.panel.firstChild;
+ for (let node of notification.childNodes) {
+ if (node.localName == "menuitem")
+ labels.push(node.getAttribute("label"));
+ }
+ is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown");
+
+ // Cleanup.
+ yield closeStream(true);
+ Perms.remove(uri, "camera");
+ Perms.remove(uri, "microphone");
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+ ok(gIdentityHandler._identityPopup.hidden,
+ "should start the test with the control center hidden");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+
+ // Cleanup before the next test
+ yield expectNoObserverCalled();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ content.location = rootDir + "get_user_media.html";
+}
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js
new file mode 100644
index 000000000..f407061a7
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+var gTests = [
+
+{
+ desc: "device sharing animation on background tabs",
+ run: function* checkAudioVideo() {
+ function* getStreamAndCheckBackgroundAnim(aAudio, aVideo, aSharing) {
+ // Get a stream
+ let popupPromise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(aAudio, aVideo);
+ yield popupPromise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ let expected = [];
+ if (aVideo)
+ expected.push("Camera");
+ if (aAudio)
+ expected.push("Microphone");
+ is((yield getMediaCaptureState()), expected.join("And"),
+ "expected stream to be shared");
+
+ // Check the attribute on the tab, and check there's no visible
+ // sharing icon on the tab
+ let tab = gBrowser.selectedTab;
+ is(tab.getAttribute("sharing"), aSharing,
+ "the tab has the attribute to show the " + aSharing + " icon");
+ let icon =
+ document.getAnonymousElementByAttribute(tab, "anonid", "sharing-icon");
+ is(window.getComputedStyle(icon).display, "none",
+ "the animated sharing icon of the tab is hidden");
+
+ // After selecting a new tab, check the attribute is still there,
+ // and the icon is now visible.
+ yield BrowserTestUtils.switchTab(gBrowser, gBrowser.addTab());
+ is(gBrowser.selectedTab.getAttribute("sharing"), "",
+ "the new tab doesn't have the 'sharing' attribute");
+ is(tab.getAttribute("sharing"), aSharing,
+ "the tab still has the 'sharing' attribute");
+ isnot(window.getComputedStyle(icon).display, "none",
+ "the animated sharing icon of the tab is now visible");
+
+ // Ensure the icon disappears when selecting the tab.
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ ok(tab.selected, "the tab with ongoing sharing is selected again");
+ is(window.getComputedStyle(icon).display, "none",
+ "the animated sharing icon is gone after selecting the tab again");
+
+ // And finally verify the attribute is removed when closing the stream.
+ yield closeStream();
+
+ // TODO(Bug 1304997): Fix the race in closeStream() and remove this
+ // promiseWaitForCondition().
+ yield promiseWaitForCondition(() => !tab.getAttribute("sharing"));
+ is(tab.getAttribute("sharing"), "",
+ "the tab no longer has the 'sharing' attribute after closing the stream");
+ }
+
+ yield getStreamAndCheckBackgroundAnim(true, true, "camera");
+ yield getStreamAndCheckBackgroundAnim(false, true, "camera");
+ yield getStreamAndCheckBackgroundAnim(true, false, "microphone");
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ content.location = rootDir + "get_user_media.html";
+}
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
new file mode 100644
index 000000000..01a544aae
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+function promiseReloadFrame(aFrameId) {
+ return ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) {
+ content.wrappedJSObject.document.getElementById(aFrameId).contentWindow.location.reload();
+ });
+}
+
+var gTests = [
+
+{
+ desc: "getUserMedia audio+video",
+ run: function* checkAudioVideo() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareDevices-notification-icon", "anchored to device icon");
+ checkDeviceSelectors(true, true);
+ is(PopupNotifications.panel.firstChild.getAttribute("popupid"),
+ "webRTC-shareDevices", "panel using devices icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({audio: true, video: true});
+ yield closeStream(false, "frame1");
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: stop sharing",
+ run: function* checkStopSharing() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ activateSecondaryAction(kActionAlways);
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ let Perms = Services.perms;
+ let uri = Services.io.newURI("https://example.com/", null, null);
+ is(Perms.testExactPermission(uri, "microphone"), Perms.ALLOW_ACTION,
+ "microphone persistently allowed");
+ is(Perms.testExactPermission(uri, "camera"), Perms.ALLOW_ACTION,
+ "camera persistently allowed");
+
+ yield stopSharing();
+
+ // The persistent permissions for the frame should have been removed.
+ is(Perms.testExactPermission(uri, "microphone"), Perms.UNKNOWN_ACTION,
+ "microphone not persistently allowed");
+ is(Perms.testExactPermission(uri, "camera"), Perms.UNKNOWN_ACTION,
+ "camera not persistently allowed");
+
+ // the stream is already closed, but this will do some cleanup anyway
+ yield closeStream(true, "frame1");
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the frame removes all sharing UI",
+ run: function* checkReloading() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("reloading the frame");
+ promise = promiseObserverCalled("recording-device-events");
+ yield promiseReloadFrame("frame1");
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the frame removes prompts",
+ run: function* checkReloadingRemovesPrompts() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ info("reloading the frame");
+ promise = promiseObserverCalled("recording-window-ended");
+ yield promiseReloadFrame("frame1");
+ yield promise;
+ yield promiseNoPopupNotification("webRTC-shareDevices");
+
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading a frame updates the sharing UI",
+ run: function* checkUpdateWhenReloading() {
+ // We'll share only the mic in the first frame, then share both in the
+ // second frame, then reload the second frame. After each step, we'll check
+ // the UI is in the correct state.
+
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, false, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, false);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Microphone", "microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: false, audio: true});
+ yield expectNoObserverCalled();
+
+ promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame2");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield checkSharingUI({video: true, audio: true});
+ yield expectNoObserverCalled();
+
+ info("reloading the second frame");
+ promise = promiseObserverCalled("recording-device-events");
+ yield promiseReloadFrame("frame2");
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield checkSharingUI({video: false, audio: true});
+ yield expectNoObserverCalled();
+
+ yield closeStream(false, "frame1");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the top level page removes all sharing UI",
+ run: function* checkReloading() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("reloading the web page");
+ promise = promiseObserverCalled("recording-device-events");
+ content.location.reload();
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+
+ // Cleanup before the next test
+ yield expectNoObserverCalled();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ let url = rootDir + "get_user_media.html";
+ content.location = 'data:text/html,<iframe id="frame1" src="' + url + '"></iframe><iframe id="frame2" src="' + url + '"></iframe>'
+}
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
new file mode 100644
index 000000000..b19065371
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+var gTests = [
+
+{
+ desc: "getUserMedia: tearing-off a tab keeps sharing indicators",
+ run: function* checkTearingOff() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("tearing off the tab");
+ let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ yield whenDelayedStartupFinished(win);
+ yield checkSharingUI({audio: true, video: true}, win);
+
+ // Clicking the global sharing indicator should open the control center in
+ // the second window.
+ ok(win.gIdentityHandler._identityPopup.hidden, "control center should be hidden");
+ let activeStreams = webrtcUI.getActiveStreams(true, false, false);
+ webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices");
+ ok(!win.gIdentityHandler._identityPopup.hidden,
+ "control center should be open in the second window");
+ ok(gIdentityHandler._identityPopup.hidden,
+ "control center should be hidden in the first window");
+ win.gIdentityHandler._identityPopup.hidden = true;
+
+ // Closing the new window should remove all sharing indicators.
+ // We need to load the content script in the first window so that we can
+ // catch the notifications fired globally when closing the second window.
+ gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ let promises = [promiseObserverCalled("recording-device-events"),
+ promiseObserverCalled("recording-window-ended")];
+ yield BrowserTestUtils.closeWindow(win);
+ yield Promise.all(promises);
+
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, runTest);
+}
+
+function runTest() {
+ // An empty tab where we can load the content script without leaving it
+ // behind at the end of the test.
+ gBrowser.addTab();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+ ok(gIdentityHandler._identityPopup.hidden,
+ "should start the test with the control center hidden");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+
+ // Cleanup before the next test
+ yield expectNoObserverCalled();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ content.location = rootDir + "get_user_media.html";
+}
diff --git a/browser/base/content/test/webrtc/get_user_media.html b/browser/base/content/test/webrtc/get_user_media.html
new file mode 100644
index 000000000..16303c62d
--- /dev/null
+++ b/browser/base/content/test/webrtc/get_user_media.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="UTF-8"></head>
+<body>
+<div id="message"></div>
+<script>
+// Specifies whether we are using fake streams to run this automation
+var useFakeStreams = true;
+try {
+ var audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev");
+ var videoDevice = SpecialPowers.getCharPref("media.video_loopback_dev");
+ dump("TEST DEVICES: Using media devices:\n");
+ dump("audio: " + audioDevice + "\nvideo: " + videoDevice + "\n");
+ useFakeStreams = false;
+} catch (e) {
+ dump("TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n");
+ useFakeStreams = true;
+}
+
+function message(m) {
+ document.getElementById("message").innerHTML = m;
+ window.parent.postMessage(m, "*");
+}
+
+var gStream;
+
+function requestDevice(aAudio, aVideo, aShare) {
+ var opts = {video: aVideo, audio: aAudio};
+ if (aShare) {
+ opts.video = {
+ mozMediaSource: aShare,
+ mediaSource: aShare
+ }
+ } else if (useFakeStreams) {
+ opts.fake = true;
+ }
+
+ window.navigator.mediaDevices.getUserMedia(opts)
+ .then(stream => {
+ gStream = stream;
+ message("ok");
+ }, err => message("error: " + err));
+}
+message("pending");
+
+function closeStream() {
+ if (!gStream)
+ return;
+ gStream.getTracks().forEach(t => t.stop());
+ gStream = null;
+ message("closed");
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/webrtc/get_user_media_content_script.js b/browser/base/content/test/webrtc/get_user_media_content_script.js
new file mode 100644
index 000000000..71b68d826
--- /dev/null
+++ b/browser/base/content/test/webrtc/get_user_media_content_script.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
+ "@mozilla.org/mediaManagerService;1",
+ "nsIMediaManagerService");
+
+const kObservedTopics = [
+ "getUserMedia:response:allow",
+ "getUserMedia:revoke",
+ "getUserMedia:response:deny",
+ "getUserMedia:request",
+ "recording-device-events",
+ "recording-window-ended"
+];
+
+var gObservedTopics = {};
+function observer(aSubject, aTopic, aData) {
+ if (!(aTopic in gObservedTopics))
+ gObservedTopics[aTopic] = 1;
+ else
+ ++gObservedTopics[aTopic];
+}
+
+kObservedTopics.forEach(topic => {
+ Services.obs.addObserver(observer, topic, false);
+});
+
+addMessageListener("Test:ExpectObserverCalled", ({data}) => {
+ sendAsyncMessage("Test:ExpectObserverCalled:Reply",
+ {count: gObservedTopics[data]});
+ if (data in gObservedTopics)
+ --gObservedTopics[data];
+});
+
+addMessageListener("Test:ExpectNoObserverCalled", data => {
+ sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
+ gObservedTopics = {};
+});
+
+function _getMediaCaptureState() {
+ let hasVideo = {};
+ let hasAudio = {};
+ let hasScreenShare = {};
+ let hasWindowShare = {};
+ MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio,
+ hasScreenShare, hasWindowShare);
+ if (hasVideo.value && hasAudio.value)
+ return "CameraAndMicrophone";
+ if (hasVideo.value)
+ return "Camera";
+ if (hasAudio.value)
+ return "Microphone";
+ if (hasScreenShare.value)
+ return "Screen";
+ if (hasWindowShare.value)
+ return "Window";
+ return "none";
+}
+
+addMessageListener("Test:GetMediaCaptureState", data => {
+ sendAsyncMessage("Test:MediaCaptureState", _getMediaCaptureState());
+});
+
+addMessageListener("Test:WaitForObserverCall", ({data}) => {
+ let topic = data;
+ Services.obs.addObserver(function observer() {
+ sendAsyncMessage("Test:ObserverCalled", topic);
+ Services.obs.removeObserver(observer, topic);
+
+ if (kObservedTopics.indexOf(topic) != -1) {
+ if (!(topic in gObservedTopics))
+ gObservedTopics[topic] = -1;
+ else
+ --gObservedTopics[topic];
+ }
+ }, topic, false);
+});
+
+addMessageListener("Test:WaitForMessage", () => {
+ content.addEventListener("message", ({data}) => {
+ sendAsyncMessage("Test:MessageReceived", data);
+ }, {once: true});
+});
diff --git a/browser/base/content/test/webrtc/head.js b/browser/base/content/test/webrtc/head.js
new file mode 100644
index 000000000..70b183773
--- /dev/null
+++ b/browser/base/content/test/webrtc/head.js
@@ -0,0 +1,453 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
+const CONTENT_SCRIPT_HELPER = getRootDirectory(gTestPath) + "get_user_media_content_script.js";
+
+function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
+ retryTimes = typeof retryTimes !== 'undefined' ? retryTimes : 30;
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= retryTimes) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+function promiseWaitForCondition(aConditionFn) {
+ let deferred = Promise.defer();
+ waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+ return deferred.promise;
+}
+
+/**
+ * Waits for a window with the given URL to exist.
+ *
+ * @param url
+ * The url of the window.
+ * @return {Promise} resolved when the window exists.
+ * @resolves to the window
+ */
+function promiseWindow(url) {
+ info("expecting a " + url + " window");
+ return new Promise(resolve => {
+ Services.obs.addObserver(function obs(win) {
+ win.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function loadHandler() {
+ win.removeEventListener("load", loadHandler);
+
+ if (win.location.href !== url) {
+ info("ignoring a window with this url: " + win.location.href);
+ return;
+ }
+
+ Services.obs.removeObserver(obs, "domwindowopened");
+ resolve(win);
+ });
+ }, "domwindowopened", false);
+ });
+}
+
+function whenDelayedStartupFinished(aWindow) {
+ return new Promise(resolve => {
+ info("Waiting for delayed startup to finish");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+function promiseIndicatorWindow() {
+ // We don't show the indicator window on Mac.
+ if ("nsISystemStatusBar" in Ci)
+ return Promise.resolve();
+
+ return promiseWindow("chrome://browser/content/webrtcIndicator.xul");
+}
+
+function* assertWebRTCIndicatorStatus(expected) {
+ let ui = Cu.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI;
+ let expectedState = expected ? "visible" : "hidden";
+ let msg = "WebRTC indicator " + expectedState;
+ if (!expected && ui.showGlobalIndicator) {
+ // It seems the global indicator is not always removed synchronously
+ // in some cases.
+ info("waiting for the global indicator to be hidden");
+ yield promiseWaitForCondition(() => !ui.showGlobalIndicator);
+ }
+ is(ui.showGlobalIndicator, !!expected, msg);
+
+ let expectVideo = false, expectAudio = false, expectScreen = false;
+ if (expected) {
+ if (expected.video)
+ expectVideo = true;
+ if (expected.audio)
+ expectAudio = true;
+ if (expected.screen)
+ expectScreen = true;
+ }
+ is(ui.showCameraIndicator, expectVideo, "camera global indicator as expected");
+ is(ui.showMicrophoneIndicator, expectAudio, "microphone global indicator as expected");
+ is(ui.showScreenSharingIndicator, expectScreen, "screen global indicator as expected");
+
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ let menu = win.document.getElementById("tabSharingMenu");
+ is(menu && !menu.hidden, !!expected, "WebRTC menu should be " + expectedState);
+ }
+
+ if (!("nsISystemStatusBar" in Ci)) {
+ if (!expected) {
+ let win = Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
+ if (win) {
+ yield new Promise((resolve, reject) => {
+ win.addEventListener("unload", function listener(e) {
+ if (e.target == win.document) {
+ win.removeEventListener("unload", listener);
+ resolve();
+ }
+ }, false);
+ });
+ }
+ }
+ let indicator = Services.wm.getEnumerator("Browser:WebRTCGlobalIndicator");
+ let hasWindow = indicator.hasMoreElements();
+ is(hasWindow, !!expected, "popup " + msg);
+ if (hasWindow) {
+ let document = indicator.getNext().document;
+ let docElt = document.documentElement;
+
+ if (document.readyState != "complete") {
+ info("Waiting for the sharing indicator's document to load");
+ let deferred = Promise.defer();
+ document.addEventListener("readystatechange",
+ function onReadyStateChange() {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", onReadyStateChange);
+ deferred.resolve();
+ });
+ yield deferred.promise;
+ }
+
+ for (let item of ["video", "audio", "screen"]) {
+ let expectedValue = (expected && expected[item]) ? "true" : "";
+ is(docElt.getAttribute("sharing" + item), expectedValue,
+ item + " global indicator attribute as expected");
+ }
+
+ ok(!indicator.hasMoreElements(), "only one global indicator window");
+ }
+ }
+}
+
+function promisePopupEvent(popup, eventSuffix) {
+ let endState = {shown: "open", hidden: "closed"}[eventSuffix];
+
+ if (popup.state == endState)
+ return Promise.resolve();
+
+ let eventType = "popup" + eventSuffix;
+ let deferred = Promise.defer();
+ popup.addEventListener(eventType, function onPopupShown(event) {
+ popup.removeEventListener(eventType, onPopupShown);
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function promiseNotificationShown(notification) {
+ let win = notification.browser.ownerGlobal;
+ if (win.PopupNotifications.panel.state == "open") {
+ return Promise.resolve();
+ }
+ let panelPromise = promisePopupEvent(win.PopupNotifications.panel, "shown");
+ notification.reshow();
+ return panelPromise;
+}
+
+function _mm() {
+ return gBrowser.selectedBrowser.messageManager;
+}
+
+function promiseObserverCalled(aTopic) {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:ObserverCalled", function listener({data}) {
+ if (data == aTopic) {
+ ok(true, "got " + aTopic + " notification");
+ mm.removeMessageListener("Test:ObserverCalled", listener);
+ resolve();
+ }
+ });
+ mm.sendAsyncMessage("Test:WaitForObserverCall", aTopic);
+ });
+}
+
+function expectObserverCalled(aTopic) {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:ExpectObserverCalled:Reply",
+ function listener({data}) {
+ is(data.count, 1, "expected notification " + aTopic);
+ mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
+ resolve();
+ });
+ mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
+ });
+}
+
+function expectNoObserverCalled() {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
+ function listener({data}) {
+ mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
+ for (let topic in data) {
+ if (data[topic])
+ is(data[topic], 0, topic + " notification unexpected");
+ }
+ resolve();
+ });
+ mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
+ });
+}
+
+function promiseMessage(aMessage, aAction) {
+ let promise = new Promise((resolve, reject) => {
+ let mm = _mm();
+ mm.addMessageListener("Test:MessageReceived", function listener({data}) {
+ is(data, aMessage, "received " + aMessage);
+ if (data == aMessage)
+ resolve();
+ else
+ reject();
+ mm.removeMessageListener("Test:MessageReceived", listener);
+ });
+ mm.sendAsyncMessage("Test:WaitForMessage");
+ });
+
+ if (aAction)
+ aAction();
+
+ return promise;
+}
+
+function promisePopupNotificationShown(aName, aAction) {
+ let deferred = Promise.defer();
+
+ PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() {
+ PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown);
+
+ ok(!!PopupNotifications.getNotification(aName), aName + " notification shown");
+ ok(PopupNotifications.isPanelOpen, "notification panel open");
+ ok(!!PopupNotifications.panel.firstChild, "notification panel populated");
+
+ deferred.resolve();
+ });
+
+ if (aAction)
+ aAction();
+
+ return deferred.promise;
+}
+
+function promisePopupNotification(aName) {
+ let deferred = Promise.defer();
+
+ waitForCondition(() => PopupNotifications.getNotification(aName),
+ () => {
+ ok(!!PopupNotifications.getNotification(aName),
+ aName + " notification appeared");
+
+ deferred.resolve();
+ }, "timeout waiting for popup notification " + aName);
+
+ return deferred.promise;
+}
+
+function promiseNoPopupNotification(aName) {
+ let deferred = Promise.defer();
+
+ waitForCondition(() => !PopupNotifications.getNotification(aName),
+ () => {
+ ok(!PopupNotifications.getNotification(aName),
+ aName + " notification removed");
+ deferred.resolve();
+ }, "timeout waiting for popup notification " + aName + " to disappear");
+
+ return deferred.promise;
+}
+
+const kActionAlways = 1;
+const kActionDeny = 2;
+const kActionNever = 3;
+
+function activateSecondaryAction(aAction) {
+ let notification = PopupNotifications.panel.firstChild;
+ notification.button.focus();
+ let popup = notification.menupopup;
+ popup.addEventListener("popupshown", function () {
+ popup.removeEventListener("popupshown", arguments.callee, false);
+
+ // Press 'down' as many time as needed to select the requested action.
+ while (aAction--)
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ // Activate
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ // One down event to open the popup
+ EventUtils.synthesizeKey("VK_DOWN",
+ { altKey: !navigator.platform.includes("Mac") });
+}
+
+function getMediaCaptureState() {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
+ resolve(data);
+ });
+ mm.sendAsyncMessage("Test:GetMediaCaptureState");
+ });
+}
+
+function* stopSharing(aType = "camera") {
+ let promiseRecordingEvent = promiseObserverCalled("recording-device-events");
+ gIdentityHandler._identityBox.click();
+ let permissions = document.getElementById("identity-popup-permission-list");
+ let cancelButton =
+ permissions.querySelector(".identity-popup-permission-icon." + aType + "-icon ~ " +
+ ".identity-popup-permission-remove-button");
+ cancelButton.click();
+ gIdentityHandler._identityPopup.hidden = true;
+ yield promiseRecordingEvent;
+ yield expectObserverCalled("getUserMedia:revoke");
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield* checkNotSharing();
+}
+
+function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType) {
+ info("requesting devices");
+ return ContentTask.spawn(gBrowser.selectedBrowser,
+ {aRequestAudio, aRequestVideo, aFrameId, aType},
+ function*(args) {
+ let global = content.wrappedJSObject;
+ if (args.aFrameId)
+ global = global.document.getElementById(args.aFrameId).contentWindow;
+ global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
+ });
+}
+
+function* closeStream(aAlreadyClosed, aFrameId) {
+ yield expectNoObserverCalled();
+
+ let promises;
+ if (!aAlreadyClosed) {
+ promises = [promiseObserverCalled("recording-device-events"),
+ promiseObserverCalled("recording-window-ended")];
+ }
+
+ info("closing the stream");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) {
+ let global = content.wrappedJSObject;
+ if (aFrameId)
+ global = global.document.getElementById(aFrameId).contentWindow;
+ global.closeStream();
+ });
+
+ if (promises)
+ yield Promise.all(promises);
+
+ yield* assertWebRTCIndicatorStatus(null);
+}
+
+function checkDeviceSelectors(aAudio, aVideo) {
+ let micSelector = document.getElementById("webRTC-selectMicrophone");
+ if (aAudio)
+ ok(!micSelector.hidden, "microphone selector visible");
+ else
+ ok(micSelector.hidden, "microphone selector hidden");
+
+ let cameraSelector = document.getElementById("webRTC-selectCamera");
+ if (aVideo)
+ ok(!cameraSelector.hidden, "camera selector visible");
+ else
+ ok(cameraSelector.hidden, "camera selector hidden");
+}
+
+function* checkSharingUI(aExpected, aWin = window) {
+ let doc = aWin.document;
+ // First check the icon above the control center (i) icon.
+ let identityBox = doc.getElementById("identity-box");
+ ok(identityBox.hasAttribute("sharing"), "sharing attribute is set");
+ let sharing = identityBox.getAttribute("sharing");
+ if (aExpected.video)
+ is(sharing, "camera", "showing camera icon on the control center icon");
+ else if (aExpected.audio)
+ is(sharing, "microphone", "showing mic icon on the control center icon");
+
+ // Then check the sharing indicators inside the control center panel.
+ identityBox.click();
+ let permissions = doc.getElementById("identity-popup-permission-list");
+ for (let id of ["microphone", "camera", "screen"]) {
+ let convertId = id => {
+ if (id == "camera")
+ return "video";
+ if (id == "microphone")
+ return "audio";
+ return id;
+ };
+ let expected = aExpected[convertId(id)];
+ is(!!aWin.gIdentityHandler._sharingState[id], !!expected,
+ "sharing state for " + id + " as expected");
+ let icon = permissions.querySelectorAll(
+ ".identity-popup-permission-icon." + id + "-icon");
+ if (expected) {
+ is(icon.length, 1, "should show " + id + " icon in control center panel");
+ ok(icon[0].classList.contains("in-use"), "icon should have the in-use class");
+ } else if (!icon.length) {
+ ok(true, "should not show " + id + " icon in the control center panel");
+ } else {
+ // This will happen if there are persistent permissions set.
+ ok(!icon[0].classList.contains("in-use"),
+ "if shown, the " + id + " icon should not have the in-use class");
+ is(icon.length, 1, "should not show more than 1 " + id + " icon");
+ }
+ }
+ aWin.gIdentityHandler._identityPopup.hidden = true;
+
+ // Check the global indicators.
+ yield* assertWebRTCIndicatorStatus(aExpected);
+}
+
+function* checkNotSharing() {
+ is((yield getMediaCaptureState()), "none", "expected nothing to be shared");
+
+ ok(!document.getElementById("identity-box").hasAttribute("sharing"),
+ "no sharing indicator on the control center icon");
+
+ yield* assertWebRTCIndicatorStatus(null);
+}