From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- browser/base/content/test/general/.eslintrc.js | 8 + .../base/content/test/general/POSTSearchEngine.xml | 6 + .../test/general/aboutHome_content_script.js | 6 + .../test/general/accounts_testRemoteCommands.html | 83 ++ .../base/content/test/general/alltabslistener.html | 8 + .../base/content/test/general/app_bug575561.html | 18 + .../test/general/app_subframe_bug575561.html | 12 + browser/base/content/test/general/audio.ogg | Bin 0 -> 14293 bytes browser/base/content/test/general/benignPage.html | 12 + browser/base/content/test/general/browser.ini | 494 ++++++++ .../test/general/browser_PageMetaData_pushstate.js | 29 + .../content/test/general/browser_aboutAccounts.js | 499 ++++++++ .../content/test/general/browser_aboutCertError.js | 409 +++++++ .../test/general/browser_aboutHealthReport.js | 139 +++ .../base/content/test/general/browser_aboutHome.js | 668 +++++++++++ .../general/browser_aboutHome_wrapsCorrectly.js | 28 + .../content/test/general/browser_aboutNetError.js | 47 + .../browser_aboutSupport_newtab_security_state.js | 26 + .../content/test/general/browser_accesskeys.js | 82 ++ .../test/general/browser_addCertException.js | 50 + .../test/general/browser_addKeywordSearch.js | 81 ++ .../test/general/browser_alltabslistener.js | 206 ++++ .../content/test/general/browser_audioTabIcon.js | 504 ++++++++ .../test/general/browser_backButtonFitts.js | 42 + .../browser_beforeunload_duplicate_dialogs.js | 76 ++ .../test/general/browser_blob-channelname.js | 11 + .../base/content/test/general/browser_blockHPKP.js | 101 ++ .../content/test/general/browser_bookmark_popup.js | 431 +++++++ .../test/general/browser_bookmark_titles.js | 98 ++ .../content/test/general/browser_bug1015721.js | 54 + .../content/test/general/browser_bug1045809.js | 68 ++ .../browser_bug1064280_changeUrlInPinnedTab.js | 36 + .../content/test/general/browser_bug1261299.js | 73 ++ .../content/test/general/browser_bug1297539.js | 114 ++ .../content/test/general/browser_bug1299667.js | 71 ++ .../base/content/test/general/browser_bug321000.js | 80 ++ .../base/content/test/general/browser_bug356571.js | 93 ++ .../base/content/test/general/browser_bug380960.js | 11 + .../base/content/test/general/browser_bug386835.js | 89 ++ .../base/content/test/general/browser_bug406216.js | 54 + .../base/content/test/general/browser_bug408415.js | 45 + .../base/content/test/general/browser_bug409481.js | 83 ++ .../base/content/test/general/browser_bug409624.js | 57 + .../base/content/test/general/browser_bug413915.js | 62 + .../base/content/test/general/browser_bug416661.js | 43 + .../base/content/test/general/browser_bug417483.js | 30 + .../base/content/test/general/browser_bug419612.js | 32 + .../base/content/test/general/browser_bug422590.js | 50 + .../base/content/test/general/browser_bug423833.js | 138 +++ .../base/content/test/general/browser_bug424101.js | 52 + .../base/content/test/general/browser_bug427559.js | 38 + .../base/content/test/general/browser_bug431826.js | 50 + .../base/content/test/general/browser_bug432599.js | 127 +++ .../base/content/test/general/browser_bug435035.js | 17 + .../base/content/test/general/browser_bug435325.js | 69 ++ .../base/content/test/general/browser_bug441778.js | 46 + .../base/content/test/general/browser_bug455852.js | 20 + .../base/content/test/general/browser_bug460146.js | 51 + .../base/content/test/general/browser_bug462289.js | 81 ++ .../base/content/test/general/browser_bug462673.js | 36 + .../base/content/test/general/browser_bug477014.js | 25 + .../base/content/test/general/browser_bug479408.js | 17 + .../test/general/browser_bug479408_sample.html | 4 + .../base/content/test/general/browser_bug481560.js | 21 + .../base/content/test/general/browser_bug484315.js | 23 + .../base/content/test/general/browser_bug491431.js | 34 + .../base/content/test/general/browser_bug495058.js | 38 + .../base/content/test/general/browser_bug517902.js | 42 + .../base/content/test/general/browser_bug519216.js | 45 + .../base/content/test/general/browser_bug520538.js | 15 + .../base/content/test/general/browser_bug521216.js | 50 + .../base/content/test/general/browser_bug533232.js | 36 + .../base/content/test/general/browser_bug537013.js | 135 +++ .../base/content/test/general/browser_bug537474.js | 8 + .../base/content/test/general/browser_bug550565.js | 44 + .../base/content/test/general/browser_bug553455.js | 1200 ++++++++++++++++++++ .../base/content/test/general/browser_bug555224.js | 40 + .../base/content/test/general/browser_bug555767.js | 54 + .../base/content/test/general/browser_bug559991.js | 42 + .../base/content/test/general/browser_bug561636.js | 370 ++++++ .../base/content/test/general/browser_bug563588.js | 30 + .../base/content/test/general/browser_bug565575.js | 14 + .../base/content/test/general/browser_bug567306.js | 50 + .../base/content/test/general/browser_bug575561.js | 97 ++ .../base/content/test/general/browser_bug575830.js | 33 + .../base/content/test/general/browser_bug577121.js | 29 + .../base/content/test/general/browser_bug578534.js | 23 + .../base/content/test/general/browser_bug579872.js | 28 + .../base/content/test/general/browser_bug580638.js | 60 + .../base/content/test/general/browser_bug580956.js | 26 + .../base/content/test/general/browser_bug581242.js | 21 + .../base/content/test/general/browser_bug581253.js | 86 ++ .../base/content/test/general/browser_bug585558.js | 153 +++ .../base/content/test/general/browser_bug585785.js | 35 + .../base/content/test/general/browser_bug585830.js | 25 + .../base/content/test/general/browser_bug590206.js | 163 +++ .../base/content/test/general/browser_bug592338.js | 163 +++ .../base/content/test/general/browser_bug594131.js | 21 + .../base/content/test/general/browser_bug595507.js | 36 + .../base/content/test/general/browser_bug596687.js | 25 + .../base/content/test/general/browser_bug597218.js | 38 + .../base/content/test/general/browser_bug609700.js | 20 + .../base/content/test/general/browser_bug623893.js | 37 + .../base/content/test/general/browser_bug624734.js | 29 + .../base/content/test/general/browser_bug633691.js | 28 + .../base/content/test/general/browser_bug647886.js | 40 + .../base/content/test/general/browser_bug655584.js | 23 + .../base/content/test/general/browser_bug664672.js | 19 + .../base/content/test/general/browser_bug676619.js | 124 ++ .../content/test/general/browser_bug678392-1.html | 12 + .../content/test/general/browser_bug678392-2.html | 12 + .../base/content/test/general/browser_bug678392.js | 191 ++++ .../base/content/test/general/browser_bug710878.js | 34 + .../base/content/test/general/browser_bug719271.js | 95 ++ .../base/content/test/general/browser_bug724239.js | 11 + .../base/content/test/general/browser_bug734076.js | 114 ++ .../base/content/test/general/browser_bug735471.js | 23 + .../base/content/test/general/browser_bug749738.js | 29 + .../test/general/browser_bug763468_perwindowpb.js | 70 ++ .../test/general/browser_bug767836_perwindowpb.js | 90 ++ .../base/content/test/general/browser_bug817947.js | 55 + .../base/content/test/general/browser_bug822367.js | 187 +++ .../base/content/test/general/browser_bug832435.js | 23 + .../base/content/test/general/browser_bug839103.js | 120 ++ .../base/content/test/general/browser_bug882977.js | 29 + .../base/content/test/general/browser_bug902156.js | 174 +++ .../base/content/test/general/browser_bug906190.js | 240 ++++ .../base/content/test/general/browser_bug963945.js | 23 + .../base/content/test/general/browser_bug970746.js | 121 ++ .../content/test/general/browser_bug970746.xhtml | 20 + .../base/content/test/general/browser_clipboard.js | 174 +++ .../test/general/browser_clipboard_pastefile.js | 62 + .../test/general/browser_contentAltClick.js | 107 ++ .../test/general/browser_contentAreaClick.js | 307 +++++ .../test/general/browser_contentSearchUI.js | 771 +++++++++++++ .../content/test/general/browser_contextmenu.js | 996 ++++++++++++++++ .../general/browser_contextmenu_childprocess.js | 84 ++ .../test/general/browser_contextmenu_input.js | 243 ++++ .../general/browser_csp_block_all_mixedcontent.js | 55 + .../base/content/test/general/browser_ctrlTab.js | 185 +++ .../general/browser_datachoices_notification.js | 221 ++++ .../content/test/general/browser_decoderDoctor.js | 122 ++ .../content/test/general/browser_devedition.js | 129 +++ .../base/content/test/general/browser_discovery.js | 162 +++ .../test/general/browser_documentnavigation.js | 266 +++++ .../browser_domFullscreen_fullscreenMode.js | 221 ++++ .../test/general/browser_double_close_tab.js | 80 ++ browser/base/content/test/general/browser_drag.js | 45 + .../content/test/general/browser_duplicateIDs.js | 8 + .../test/general/browser_e10s_about_process.js | 114 ++ .../test/general/browser_e10s_chrome_process.js | 150 +++ .../test/general/browser_e10s_javascript.js | 11 + .../test/general/browser_e10s_switchbrowser.js | 261 +++++ .../content/test/general/browser_favicon_change.js | 41 + .../browser_favicon_change_not_in_document.js | 34 + .../content/test/general/browser_feed_discovery.js | 33 + .../content/test/general/browser_findbarClose.js | 35 + .../content/test/general/browser_focusonkeydown.js | 26 + .../test/general/browser_fullscreen-window-open.js | 347 ++++++ .../content/test/general/browser_fxa_migrate.js | 18 + .../content/test/general/browser_fxa_oauth.html | 30 + .../base/content/test/general/browser_fxa_oauth.js | 327 ++++++ .../test/general/browser_fxa_oauth_with_keys.html | 33 + .../test/general/browser_fxa_web_channel.html | 138 +++ .../test/general/browser_fxa_web_channel.js | 210 ++++ .../content/test/general/browser_fxaccounts.js | 261 +++++ .../test/general/browser_gZipOfflineChild.js | 80 ++ .../content/test/general/browser_gestureSupport.js | 670 +++++++++++ .../test/general/browser_getshortcutoruri.js | 143 +++ .../content/test/general/browser_hide_removing.js | 39 + .../base/content/test/general/browser_homeDrop.js | 90 ++ .../content/test/general/browser_identity_UI.js | 146 +++ .../test/general/browser_insecureLoginForms.js | 162 +++ ...rowser_invalid_uri_back_forward_manipulation.js | 39 + .../test/general/browser_keywordBookmarklets.js | 54 + .../content/test/general/browser_keywordSearch.js | 88 ++ .../test/general/browser_keywordSearch_postData.js | 94 ++ .../test/general/browser_lastAccessedTab.js | 47 + .../content/test/general/browser_mcb_redirect.js | 314 +++++ .../test/general/browser_menuButtonBadgeManager.js | 46 + .../test/general/browser_menuButtonFitts.js | 32 + .../test/general/browser_middleMouse_noJSPaste.js | 34 + .../base/content/test/general/browser_minimize.js | 18 + .../browser_misused_characters_in_strings.js | 244 ++++ .../general/browser_mixedContentFramesOnHttp.js | 34 + .../general/browser_mixedContentFromOnunload.js | 49 + .../general/browser_mixed_content_cert_override.js | 54 + .../general/browser_mixedcontent_securityflags.js | 70 ++ .../browser_modifiedclick_inherit_principal.js | 30 + .../content/test/general/browser_newTabDrop.js | 99 ++ .../content/test/general/browser_newWindowDrop.js | 120 ++ .../test/general/browser_newwindow_focus.js | 96 ++ .../test/general/browser_no_mcb_on_http_site.js | 106 ++ .../general/browser_offlineQuotaNotification.js | 95 ++ .../content/test/general/browser_overflowScroll.js | 91 ++ .../base/content/test/general/browser_pageInfo.js | 38 + .../test/general/browser_page_style_menu.js | 97 ++ .../test/general/browser_page_style_menu_update.js | 67 ++ .../test/general/browser_pageinfo_svg_image.js | 38 + .../content/test/general/browser_parsable_css.js | 376 ++++++ .../test/general/browser_parsable_script.js | 132 +++ .../content/test/general/browser_permissions.js | 202 ++++ .../content/test/general/browser_pinnedTabs.js | 75 ++ .../content/test/general/browser_plainTextLinks.js | 146 +++ .../content/test/general/browser_printpreview.js | 74 ++ .../general/browser_private_browsing_window.js | 65 ++ .../test/general/browser_private_no_prompt.js | 12 + .../test/general/browser_purgehistory_clears_sh.js | 60 + .../content/test/general/browser_refreshBlocker.js | 135 +++ ...owser_registerProtocolHandler_notification.html | 15 + ...browser_registerProtocolHandler_notification.js | 43 + .../content/test/general/browser_relatedTabs.js | 51 + .../test/general/browser_remoteTroubleshoot.js | 93 ++ .../browser_remoteWebNavigation_postdata.js | 50 + .../test/general/browser_removeTabsToTheEnd.js | 24 + .../test/general/browser_restore_isAppTab.js | 160 +++ .../browser_sanitize-passwordDisabledHosts.js | 39 + .../general/browser_sanitize-sitepermissions.js | 52 + .../test/general/browser_sanitize-timespans.js | 733 ++++++++++++ .../content/test/general/browser_sanitizeDialog.js | 1027 +++++++++++++++++ .../test/general/browser_save_link-perwindowpb.js | 199 ++++ .../browser_save_link_when_window_navigates.js | 173 +++ .../browser_save_private_link_perwindowpb.js | 116 ++ .../content/test/general/browser_save_video.js | 87 ++ .../test/general/browser_save_video_frame.js | 125 ++ browser/base/content/test/general/browser_scope.js | 10 + .../test/general/browser_selectTabAtIndex.js | 81 ++ .../content/test/general/browser_selectpopup.js | 563 +++++++++ .../test/general/browser_ssl_error_reports.js | 174 +++ .../base/content/test/general/browser_star_hsts.js | 85 ++ .../content/test/general/browser_star_hsts.sjs | 13 + .../general/browser_subframe_favicons_not_used.js | 20 + .../base/content/test/general/browser_syncui.js | 205 ++++ .../base/content/test/general/browser_tabDrop.js | 103 ++ .../content/test/general/browser_tabReorder.js | 49 + .../general/browser_tab_close_dependent_window.js | 24 + .../test/general/browser_tab_detach_restore.js | 34 + .../general/browser_tab_drag_drop_perwindow.js | 216 ++++ .../content/test/general/browser_tab_dragdrop.js | 186 +++ .../content/test/general/browser_tab_dragdrop2.js | 57 + .../test/general/browser_tab_dragdrop2_frame1.xul | 169 +++ .../test/general/browser_tabbar_big_widgets.js | 29 + .../base/content/test/general/browser_tabfocus.js | 565 +++++++++ .../test/general/browser_tabkeynavigation.js | 156 +++ .../test/general/browser_tabopen_reflows.js | 157 +++ .../general/browser_tabs_close_beforeunload.js | 49 + .../content/test/general/browser_tabs_isActive.js | 152 +++ .../content/test/general/browser_tabs_owner.js | 44 + ...r_testOpenNewRemoteTabsFromNonRemoteBrowsers.js | 126 ++ .../content/test/general/browser_trackingUI_1.js | 170 +++ .../content/test/general/browser_trackingUI_2.js | 96 ++ .../content/test/general/browser_trackingUI_3.js | 52 + .../content/test/general/browser_trackingUI_4.js | 109 ++ .../content/test/general/browser_trackingUI_5.js | 131 +++ .../content/test/general/browser_trackingUI_6.js | 46 + .../test/general/browser_trackingUI_telemetry.js | 145 +++ .../content/test/general/browser_typeAheadFind.js | 22 + .../general/browser_unknownContentType_title.js | 33 + .../content/test/general/browser_unloaddialogs.js | 41 + .../content/test/general/browser_utilityOverlay.js | 112 ++ .../general/browser_viewSourceInTabOnViewSource.js | 55 + .../test/general/browser_visibleFindSelection.js | 52 + .../content/test/general/browser_visibleTabs.js | 97 ++ .../browser_visibleTabs_bookmarkAllPages.js | 34 + .../general/browser_visibleTabs_bookmarkAllTabs.js | 66 ++ .../general/browser_visibleTabs_contextMenu.js | 72 ++ .../test/general/browser_visibleTabs_tabPreview.js | 41 + .../content/test/general/browser_web_channel.html | 189 +++ .../content/test/general/browser_web_channel.js | 436 +++++++ .../test/general/browser_web_channel_iframe.html | 96 ++ .../test/general/browser_windowactivation.js | 183 +++ .../test/general/browser_windowopen_reflows.js | 117 ++ .../content/test/general/browser_zbug569342.js | 80 ++ .../general/bug1262648_string_with_newlines.dtd | 3 + .../base/content/test/general/bug364677-data.xml | 5 + .../test/general/bug364677-data.xml^headers^ | 1 + .../base/content/test/general/bug395533-data.txt | 6 + browser/base/content/test/general/bug592338.html | 24 + browser/base/content/test/general/bug792517-2.html | 5 + browser/base/content/test/general/bug792517.html | 5 + browser/base/content/test/general/bug792517.sjs | 13 + browser/base/content/test/general/bug839103.css | 1 + .../content/test/general/clipboard_pastefile.html | 37 + .../content/test/general/close_beforeunload.html | 8 + .../close_beforeunload_opens_second_tab.html | 3 + .../base/content/test/general/contentSearchUI.html | 21 + .../base/content/test/general/contentSearchUI.js | 209 ++++ .../content/test/general/content_aboutAccounts.js | 87 ++ .../content/test/general/contextmenu_common.js | 324 ++++++ .../base/content/test/general/ctxmenu-image.png | Bin 0 -> 5401 bytes browser/base/content/test/general/discovery.html | 8 + .../base/content/test/general/download_page.html | 47 + browser/base/content/test/general/dummy_page.html | 9 + .../base/content/test/general/feed_discovery.html | 73 ++ browser/base/content/test/general/feed_tab.html | 17 + .../content/test/general/file_bug1045809_1.html | 7 + .../content/test/general/file_bug1045809_2.html | 7 + .../content/test/general/file_bug822367_1.html | 18 + .../base/content/test/general/file_bug822367_1.js | 1 + .../content/test/general/file_bug822367_2.html | 16 + .../content/test/general/file_bug822367_3.html | 27 + .../content/test/general/file_bug822367_4.html | 18 + .../base/content/test/general/file_bug822367_4.js | 1 + .../content/test/general/file_bug822367_4B.html | 18 + .../content/test/general/file_bug822367_5.html | 24 + .../content/test/general/file_bug822367_6.html | 16 + .../base/content/test/general/file_bug902156.js | 5 + .../content/test/general/file_bug902156_1.html | 15 + .../content/test/general/file_bug902156_2.html | 17 + .../content/test/general/file_bug902156_3.html | 15 + .../base/content/test/general/file_bug906190.js | 5 + .../base/content/test/general/file_bug906190.sjs | 17 + .../content/test/general/file_bug906190_1.html | 15 + .../content/test/general/file_bug906190_2.html | 15 + .../content/test/general/file_bug906190_3_4.html | 14 + .../test/general/file_bug906190_redirected.html | 15 + .../test/general/file_bug970276_favicon1.ico | Bin 0 -> 1406 bytes .../test/general/file_bug970276_favicon2.ico | Bin 0 -> 1406 bytes .../test/general/file_bug970276_popup1.html | 14 + .../test/general/file_bug970276_popup2.html | 12 + .../general/file_csp_block_all_mixedcontent.html | 9 + .../general/file_csp_block_all_mixedcontent.js | 3 + .../general/file_documentnavigation_frameset.html | 12 + .../test/general/file_double_close_tab.html | 15 + .../content/test/general/file_favicon_change.html | 13 + .../file_favicon_change_not_in_document.html | 21 + .../test/general/file_fullscreen-window-open.html | 24 + .../content/test/general/file_generic_favicon.ico | Bin 0 -> 1406 bytes .../content/test/general/file_mediaPlayback.html | 2 + .../general/file_mixedContentFramesOnHttp.html | 14 + .../general/file_mixedContentFromOnunload.html | 18 + .../file_mixedContentFromOnunload_test1.html | 14 + .../file_mixedContentFromOnunload_test2.html | 15 + .../test/general/file_mixedPassiveContent.html | 13 + .../content/test/general/file_trackingUI_6.html | 16 + .../base/content/test/general/file_trackingUI_6.js | 2 + .../test/general/file_trackingUI_6.js^headers^ | 1 + .../content/test/general/file_with_favicon.html | 12 + .../content/test/general/fxa_profile_handler.sjs | 34 + .../test/general/gZipOfflineChild.cacheManifest | 2 + .../gZipOfflineChild.cacheManifest^headers^ | 1 + .../content/test/general/gZipOfflineChild.html | Bin 0 -> 303 bytes .../test/general/gZipOfflineChild.html^headers^ | 2 + .../general/gZipOfflineChild_uncompressed.html | 21 + browser/base/content/test/general/head.js | 1069 +++++++++++++++++ browser/base/content/test/general/head_plain.js | 27 + .../content/test/general/healthreport_pingData.js | 17 + .../general/healthreport_testRemoteCommands.html | 243 ++++ .../base/content/test/general/insecure_opener.html | 9 + browser/base/content/test/general/mochitest.ini | 27 + browser/base/content/test/general/moz.png | Bin 0 -> 580 bytes .../general/navigating_window_with_download.html | 7 + .../base/content/test/general/offlineByDefault.js | 17 + .../test/general/offlineChild.cacheManifest | 2 + .../general/offlineChild.cacheManifest^headers^ | 1 + .../base/content/test/general/offlineChild.html | 20 + .../test/general/offlineChild2.cacheManifest | 2 + .../general/offlineChild2.cacheManifest^headers^ | 1 + .../base/content/test/general/offlineChild2.html | 20 + .../test/general/offlineEvent.cacheManifest | 2 + .../general/offlineEvent.cacheManifest^headers^ | 1 + .../base/content/test/general/offlineEvent.html | 9 + .../general/offlineQuotaNotification.cacheManifest | 7 + .../test/general/offlineQuotaNotification.html | 9 + .../content/test/general/page_style_sample.html | 41 + .../content/test/general/parsingTestHelpers.jsm | 131 +++ browser/base/content/test/general/permissions.html | 14 + .../base/content/test/general/pinning_headers.sjs | 23 + .../base/content/test/general/print_postdata.sjs | 22 + .../base/content/test/general/refresh_header.sjs | 24 + browser/base/content/test/general/refresh_meta.sjs | 36 + .../test/general/searchSuggestionEngine.sjs | 9 + .../test/general/searchSuggestionEngine.xml | 9 + .../test/general/searchSuggestionEngine2.xml | 9 + .../content/test/general/ssl_error_reports.sjs | 91 ++ .../content/test/general/subtst_contextmenu.html | 73 ++ .../test/general/subtst_contextmenu_input.html | 29 + .../test/general/subtst_contextmenu_xul.xul | 9 + browser/base/content/test/general/svg_image.html | 11 + .../general/test-mixedcontent-securityerrors.html | 21 + .../base/content/test/general/test_bug364677.html | 32 + .../base/content/test/general/test_bug395533.html | 38 + .../base/content/test/general/test_bug435035.html | 1 + .../base/content/test/general/test_bug462673.html | 18 + .../base/content/test/general/test_bug628179.html | 10 + .../base/content/test/general/test_bug839103.html | 10 + .../base/content/test/general/test_bug959531.html | 9 + .../general/test_mcb_double_redirect_image.html | 23 + .../content/test/general/test_mcb_redirect.html | 15 + .../base/content/test/general/test_mcb_redirect.js | 5 + .../content/test/general/test_mcb_redirect.sjs | 22 + .../test/general/test_mcb_redirect_image.html | 23 + .../test/general/test_no_mcb_on_http_site_font.css | 10 + .../general/test_no_mcb_on_http_site_font.html | 47 + .../general/test_no_mcb_on_http_site_font2.css | 1 + .../general/test_no_mcb_on_http_site_font2.html | 48 + .../test/general/test_no_mcb_on_http_site_img.css | 3 + .../test/general/test_no_mcb_on_http_site_img.html | 47 + .../test/general/test_offlineNotification.html | 129 +++ .../content/test/general/test_offline_gzip.html | 21 + .../test/general/test_process_flags_chrome.html | 10 + .../test/general/test_remoteTroubleshoot.html | 50 + browser/base/content/test/general/title_test.svg | 59 + .../base/content/test/general/trackingPage.html | 12 + .../test/general/unknownContentType_file.pif | 1 + .../general/unknownContentType_file.pif^headers^ | 1 + browser/base/content/test/general/video.ogg | Bin 0 -> 285310 bytes browser/base/content/test/general/web_video.html | 10 + browser/base/content/test/general/web_video1.ogv | Bin 0 -> 28942 bytes .../content/test/general/web_video1.ogv^headers^ | 3 + browser/base/content/test/general/zoom_test.html | 14 + 411 files changed, 35369 insertions(+) create mode 100644 browser/base/content/test/general/.eslintrc.js create mode 100644 browser/base/content/test/general/POSTSearchEngine.xml create mode 100644 browser/base/content/test/general/aboutHome_content_script.js create mode 100644 browser/base/content/test/general/accounts_testRemoteCommands.html create mode 100644 browser/base/content/test/general/alltabslistener.html create mode 100644 browser/base/content/test/general/app_bug575561.html create mode 100644 browser/base/content/test/general/app_subframe_bug575561.html create mode 100644 browser/base/content/test/general/audio.ogg create mode 100644 browser/base/content/test/general/benignPage.html create mode 100644 browser/base/content/test/general/browser.ini create mode 100644 browser/base/content/test/general/browser_PageMetaData_pushstate.js create mode 100644 browser/base/content/test/general/browser_aboutAccounts.js create mode 100644 browser/base/content/test/general/browser_aboutCertError.js create mode 100644 browser/base/content/test/general/browser_aboutHealthReport.js create mode 100644 browser/base/content/test/general/browser_aboutHome.js create mode 100644 browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js create mode 100644 browser/base/content/test/general/browser_aboutNetError.js create mode 100644 browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js create mode 100644 browser/base/content/test/general/browser_accesskeys.js create mode 100644 browser/base/content/test/general/browser_addCertException.js create mode 100644 browser/base/content/test/general/browser_addKeywordSearch.js create mode 100644 browser/base/content/test/general/browser_alltabslistener.js create mode 100644 browser/base/content/test/general/browser_audioTabIcon.js create mode 100644 browser/base/content/test/general/browser_backButtonFitts.js create mode 100644 browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js create mode 100644 browser/base/content/test/general/browser_blob-channelname.js create mode 100644 browser/base/content/test/general/browser_blockHPKP.js create mode 100644 browser/base/content/test/general/browser_bookmark_popup.js create mode 100644 browser/base/content/test/general/browser_bookmark_titles.js create mode 100644 browser/base/content/test/general/browser_bug1015721.js create mode 100644 browser/base/content/test/general/browser_bug1045809.js create mode 100644 browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js create mode 100644 browser/base/content/test/general/browser_bug1261299.js create mode 100644 browser/base/content/test/general/browser_bug1297539.js create mode 100644 browser/base/content/test/general/browser_bug1299667.js create mode 100644 browser/base/content/test/general/browser_bug321000.js create mode 100644 browser/base/content/test/general/browser_bug356571.js create mode 100644 browser/base/content/test/general/browser_bug380960.js create mode 100644 browser/base/content/test/general/browser_bug386835.js create mode 100644 browser/base/content/test/general/browser_bug406216.js create mode 100644 browser/base/content/test/general/browser_bug408415.js create mode 100644 browser/base/content/test/general/browser_bug409481.js create mode 100644 browser/base/content/test/general/browser_bug409624.js create mode 100644 browser/base/content/test/general/browser_bug413915.js create mode 100644 browser/base/content/test/general/browser_bug416661.js create mode 100644 browser/base/content/test/general/browser_bug417483.js create mode 100644 browser/base/content/test/general/browser_bug419612.js create mode 100644 browser/base/content/test/general/browser_bug422590.js create mode 100644 browser/base/content/test/general/browser_bug423833.js create mode 100644 browser/base/content/test/general/browser_bug424101.js create mode 100644 browser/base/content/test/general/browser_bug427559.js create mode 100644 browser/base/content/test/general/browser_bug431826.js create mode 100644 browser/base/content/test/general/browser_bug432599.js create mode 100644 browser/base/content/test/general/browser_bug435035.js create mode 100644 browser/base/content/test/general/browser_bug435325.js create mode 100644 browser/base/content/test/general/browser_bug441778.js create mode 100644 browser/base/content/test/general/browser_bug455852.js create mode 100644 browser/base/content/test/general/browser_bug460146.js create mode 100644 browser/base/content/test/general/browser_bug462289.js create mode 100644 browser/base/content/test/general/browser_bug462673.js create mode 100644 browser/base/content/test/general/browser_bug477014.js create mode 100644 browser/base/content/test/general/browser_bug479408.js create mode 100644 browser/base/content/test/general/browser_bug479408_sample.html create mode 100644 browser/base/content/test/general/browser_bug481560.js create mode 100644 browser/base/content/test/general/browser_bug484315.js create mode 100644 browser/base/content/test/general/browser_bug491431.js create mode 100644 browser/base/content/test/general/browser_bug495058.js create mode 100644 browser/base/content/test/general/browser_bug517902.js create mode 100644 browser/base/content/test/general/browser_bug519216.js create mode 100644 browser/base/content/test/general/browser_bug520538.js create mode 100644 browser/base/content/test/general/browser_bug521216.js create mode 100644 browser/base/content/test/general/browser_bug533232.js create mode 100644 browser/base/content/test/general/browser_bug537013.js create mode 100644 browser/base/content/test/general/browser_bug537474.js create mode 100644 browser/base/content/test/general/browser_bug550565.js create mode 100644 browser/base/content/test/general/browser_bug553455.js create mode 100644 browser/base/content/test/general/browser_bug555224.js create mode 100644 browser/base/content/test/general/browser_bug555767.js create mode 100644 browser/base/content/test/general/browser_bug559991.js create mode 100644 browser/base/content/test/general/browser_bug561636.js create mode 100644 browser/base/content/test/general/browser_bug563588.js create mode 100644 browser/base/content/test/general/browser_bug565575.js create mode 100644 browser/base/content/test/general/browser_bug567306.js create mode 100644 browser/base/content/test/general/browser_bug575561.js create mode 100644 browser/base/content/test/general/browser_bug575830.js create mode 100644 browser/base/content/test/general/browser_bug577121.js create mode 100644 browser/base/content/test/general/browser_bug578534.js create mode 100644 browser/base/content/test/general/browser_bug579872.js create mode 100644 browser/base/content/test/general/browser_bug580638.js create mode 100644 browser/base/content/test/general/browser_bug580956.js create mode 100644 browser/base/content/test/general/browser_bug581242.js create mode 100644 browser/base/content/test/general/browser_bug581253.js create mode 100644 browser/base/content/test/general/browser_bug585558.js create mode 100644 browser/base/content/test/general/browser_bug585785.js create mode 100644 browser/base/content/test/general/browser_bug585830.js create mode 100644 browser/base/content/test/general/browser_bug590206.js create mode 100644 browser/base/content/test/general/browser_bug592338.js create mode 100644 browser/base/content/test/general/browser_bug594131.js create mode 100644 browser/base/content/test/general/browser_bug595507.js create mode 100644 browser/base/content/test/general/browser_bug596687.js create mode 100644 browser/base/content/test/general/browser_bug597218.js create mode 100644 browser/base/content/test/general/browser_bug609700.js create mode 100644 browser/base/content/test/general/browser_bug623893.js create mode 100644 browser/base/content/test/general/browser_bug624734.js create mode 100644 browser/base/content/test/general/browser_bug633691.js create mode 100644 browser/base/content/test/general/browser_bug647886.js create mode 100644 browser/base/content/test/general/browser_bug655584.js create mode 100644 browser/base/content/test/general/browser_bug664672.js create mode 100644 browser/base/content/test/general/browser_bug676619.js create mode 100644 browser/base/content/test/general/browser_bug678392-1.html create mode 100644 browser/base/content/test/general/browser_bug678392-2.html create mode 100644 browser/base/content/test/general/browser_bug678392.js create mode 100644 browser/base/content/test/general/browser_bug710878.js create mode 100644 browser/base/content/test/general/browser_bug719271.js create mode 100644 browser/base/content/test/general/browser_bug724239.js create mode 100644 browser/base/content/test/general/browser_bug734076.js create mode 100644 browser/base/content/test/general/browser_bug735471.js create mode 100644 browser/base/content/test/general/browser_bug749738.js create mode 100644 browser/base/content/test/general/browser_bug763468_perwindowpb.js create mode 100644 browser/base/content/test/general/browser_bug767836_perwindowpb.js create mode 100644 browser/base/content/test/general/browser_bug817947.js create mode 100644 browser/base/content/test/general/browser_bug822367.js create mode 100644 browser/base/content/test/general/browser_bug832435.js create mode 100644 browser/base/content/test/general/browser_bug839103.js create mode 100644 browser/base/content/test/general/browser_bug882977.js create mode 100644 browser/base/content/test/general/browser_bug902156.js create mode 100644 browser/base/content/test/general/browser_bug906190.js create mode 100644 browser/base/content/test/general/browser_bug963945.js create mode 100644 browser/base/content/test/general/browser_bug970746.js create mode 100644 browser/base/content/test/general/browser_bug970746.xhtml create mode 100644 browser/base/content/test/general/browser_clipboard.js create mode 100644 browser/base/content/test/general/browser_clipboard_pastefile.js create mode 100644 browser/base/content/test/general/browser_contentAltClick.js create mode 100644 browser/base/content/test/general/browser_contentAreaClick.js create mode 100644 browser/base/content/test/general/browser_contentSearchUI.js create mode 100644 browser/base/content/test/general/browser_contextmenu.js create mode 100644 browser/base/content/test/general/browser_contextmenu_childprocess.js create mode 100644 browser/base/content/test/general/browser_contextmenu_input.js create mode 100644 browser/base/content/test/general/browser_csp_block_all_mixedcontent.js create mode 100644 browser/base/content/test/general/browser_ctrlTab.js create mode 100644 browser/base/content/test/general/browser_datachoices_notification.js create mode 100644 browser/base/content/test/general/browser_decoderDoctor.js create mode 100644 browser/base/content/test/general/browser_devedition.js create mode 100644 browser/base/content/test/general/browser_discovery.js create mode 100644 browser/base/content/test/general/browser_documentnavigation.js create mode 100644 browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js create mode 100644 browser/base/content/test/general/browser_double_close_tab.js create mode 100644 browser/base/content/test/general/browser_drag.js create mode 100644 browser/base/content/test/general/browser_duplicateIDs.js create mode 100644 browser/base/content/test/general/browser_e10s_about_process.js create mode 100644 browser/base/content/test/general/browser_e10s_chrome_process.js create mode 100644 browser/base/content/test/general/browser_e10s_javascript.js create mode 100644 browser/base/content/test/general/browser_e10s_switchbrowser.js create mode 100644 browser/base/content/test/general/browser_favicon_change.js create mode 100644 browser/base/content/test/general/browser_favicon_change_not_in_document.js create mode 100644 browser/base/content/test/general/browser_feed_discovery.js create mode 100644 browser/base/content/test/general/browser_findbarClose.js create mode 100644 browser/base/content/test/general/browser_focusonkeydown.js create mode 100644 browser/base/content/test/general/browser_fullscreen-window-open.js create mode 100644 browser/base/content/test/general/browser_fxa_migrate.js create mode 100644 browser/base/content/test/general/browser_fxa_oauth.html create mode 100644 browser/base/content/test/general/browser_fxa_oauth.js create mode 100644 browser/base/content/test/general/browser_fxa_oauth_with_keys.html create mode 100644 browser/base/content/test/general/browser_fxa_web_channel.html create mode 100644 browser/base/content/test/general/browser_fxa_web_channel.js create mode 100644 browser/base/content/test/general/browser_fxaccounts.js create mode 100644 browser/base/content/test/general/browser_gZipOfflineChild.js create mode 100644 browser/base/content/test/general/browser_gestureSupport.js create mode 100644 browser/base/content/test/general/browser_getshortcutoruri.js create mode 100644 browser/base/content/test/general/browser_hide_removing.js create mode 100644 browser/base/content/test/general/browser_homeDrop.js create mode 100644 browser/base/content/test/general/browser_identity_UI.js create mode 100644 browser/base/content/test/general/browser_insecureLoginForms.js create mode 100644 browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js create mode 100644 browser/base/content/test/general/browser_keywordBookmarklets.js create mode 100644 browser/base/content/test/general/browser_keywordSearch.js create mode 100644 browser/base/content/test/general/browser_keywordSearch_postData.js create mode 100644 browser/base/content/test/general/browser_lastAccessedTab.js create mode 100644 browser/base/content/test/general/browser_mcb_redirect.js create mode 100644 browser/base/content/test/general/browser_menuButtonBadgeManager.js create mode 100644 browser/base/content/test/general/browser_menuButtonFitts.js create mode 100644 browser/base/content/test/general/browser_middleMouse_noJSPaste.js create mode 100644 browser/base/content/test/general/browser_minimize.js create mode 100644 browser/base/content/test/general/browser_misused_characters_in_strings.js create mode 100644 browser/base/content/test/general/browser_mixedContentFramesOnHttp.js create mode 100644 browser/base/content/test/general/browser_mixedContentFromOnunload.js create mode 100644 browser/base/content/test/general/browser_mixed_content_cert_override.js create mode 100644 browser/base/content/test/general/browser_mixedcontent_securityflags.js create mode 100644 browser/base/content/test/general/browser_modifiedclick_inherit_principal.js create mode 100644 browser/base/content/test/general/browser_newTabDrop.js create mode 100644 browser/base/content/test/general/browser_newWindowDrop.js create mode 100644 browser/base/content/test/general/browser_newwindow_focus.js create mode 100644 browser/base/content/test/general/browser_no_mcb_on_http_site.js create mode 100644 browser/base/content/test/general/browser_offlineQuotaNotification.js create mode 100644 browser/base/content/test/general/browser_overflowScroll.js create mode 100644 browser/base/content/test/general/browser_pageInfo.js create mode 100644 browser/base/content/test/general/browser_page_style_menu.js create mode 100644 browser/base/content/test/general/browser_page_style_menu_update.js create mode 100644 browser/base/content/test/general/browser_pageinfo_svg_image.js create mode 100644 browser/base/content/test/general/browser_parsable_css.js create mode 100644 browser/base/content/test/general/browser_parsable_script.js create mode 100644 browser/base/content/test/general/browser_permissions.js create mode 100644 browser/base/content/test/general/browser_pinnedTabs.js create mode 100644 browser/base/content/test/general/browser_plainTextLinks.js create mode 100644 browser/base/content/test/general/browser_printpreview.js create mode 100644 browser/base/content/test/general/browser_private_browsing_window.js create mode 100644 browser/base/content/test/general/browser_private_no_prompt.js create mode 100644 browser/base/content/test/general/browser_purgehistory_clears_sh.js create mode 100644 browser/base/content/test/general/browser_refreshBlocker.js create mode 100644 browser/base/content/test/general/browser_registerProtocolHandler_notification.html create mode 100644 browser/base/content/test/general/browser_registerProtocolHandler_notification.js create mode 100644 browser/base/content/test/general/browser_relatedTabs.js create mode 100644 browser/base/content/test/general/browser_remoteTroubleshoot.js create mode 100644 browser/base/content/test/general/browser_remoteWebNavigation_postdata.js create mode 100644 browser/base/content/test/general/browser_removeTabsToTheEnd.js create mode 100644 browser/base/content/test/general/browser_restore_isAppTab.js create mode 100644 browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js create mode 100644 browser/base/content/test/general/browser_sanitize-sitepermissions.js create mode 100644 browser/base/content/test/general/browser_sanitize-timespans.js create mode 100644 browser/base/content/test/general/browser_sanitizeDialog.js create mode 100644 browser/base/content/test/general/browser_save_link-perwindowpb.js create mode 100644 browser/base/content/test/general/browser_save_link_when_window_navigates.js create mode 100644 browser/base/content/test/general/browser_save_private_link_perwindowpb.js create mode 100644 browser/base/content/test/general/browser_save_video.js create mode 100644 browser/base/content/test/general/browser_save_video_frame.js create mode 100644 browser/base/content/test/general/browser_scope.js create mode 100644 browser/base/content/test/general/browser_selectTabAtIndex.js create mode 100644 browser/base/content/test/general/browser_selectpopup.js create mode 100644 browser/base/content/test/general/browser_ssl_error_reports.js create mode 100644 browser/base/content/test/general/browser_star_hsts.js create mode 100644 browser/base/content/test/general/browser_star_hsts.sjs create mode 100644 browser/base/content/test/general/browser_subframe_favicons_not_used.js create mode 100644 browser/base/content/test/general/browser_syncui.js create mode 100644 browser/base/content/test/general/browser_tabDrop.js create mode 100644 browser/base/content/test/general/browser_tabReorder.js create mode 100644 browser/base/content/test/general/browser_tab_close_dependent_window.js create mode 100644 browser/base/content/test/general/browser_tab_detach_restore.js create mode 100644 browser/base/content/test/general/browser_tab_drag_drop_perwindow.js create mode 100644 browser/base/content/test/general/browser_tab_dragdrop.js create mode 100644 browser/base/content/test/general/browser_tab_dragdrop2.js create mode 100644 browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul create mode 100644 browser/base/content/test/general/browser_tabbar_big_widgets.js create mode 100644 browser/base/content/test/general/browser_tabfocus.js create mode 100644 browser/base/content/test/general/browser_tabkeynavigation.js create mode 100644 browser/base/content/test/general/browser_tabopen_reflows.js create mode 100644 browser/base/content/test/general/browser_tabs_close_beforeunload.js create mode 100644 browser/base/content/test/general/browser_tabs_isActive.js create mode 100644 browser/base/content/test/general/browser_tabs_owner.js create mode 100644 browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js create mode 100644 browser/base/content/test/general/browser_trackingUI_1.js create mode 100644 browser/base/content/test/general/browser_trackingUI_2.js create mode 100644 browser/base/content/test/general/browser_trackingUI_3.js create mode 100644 browser/base/content/test/general/browser_trackingUI_4.js create mode 100644 browser/base/content/test/general/browser_trackingUI_5.js create mode 100644 browser/base/content/test/general/browser_trackingUI_6.js create mode 100644 browser/base/content/test/general/browser_trackingUI_telemetry.js create mode 100644 browser/base/content/test/general/browser_typeAheadFind.js create mode 100644 browser/base/content/test/general/browser_unknownContentType_title.js create mode 100644 browser/base/content/test/general/browser_unloaddialogs.js create mode 100644 browser/base/content/test/general/browser_utilityOverlay.js create mode 100644 browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js create mode 100644 browser/base/content/test/general/browser_visibleFindSelection.js create mode 100644 browser/base/content/test/general/browser_visibleTabs.js create mode 100644 browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js create mode 100644 browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js create mode 100644 browser/base/content/test/general/browser_visibleTabs_contextMenu.js create mode 100644 browser/base/content/test/general/browser_visibleTabs_tabPreview.js create mode 100644 browser/base/content/test/general/browser_web_channel.html create mode 100644 browser/base/content/test/general/browser_web_channel.js create mode 100644 browser/base/content/test/general/browser_web_channel_iframe.html create mode 100644 browser/base/content/test/general/browser_windowactivation.js create mode 100644 browser/base/content/test/general/browser_windowopen_reflows.js create mode 100644 browser/base/content/test/general/browser_zbug569342.js create mode 100644 browser/base/content/test/general/bug1262648_string_with_newlines.dtd create mode 100644 browser/base/content/test/general/bug364677-data.xml create mode 100644 browser/base/content/test/general/bug364677-data.xml^headers^ create mode 100644 browser/base/content/test/general/bug395533-data.txt create mode 100644 browser/base/content/test/general/bug592338.html create mode 100644 browser/base/content/test/general/bug792517-2.html create mode 100644 browser/base/content/test/general/bug792517.html create mode 100644 browser/base/content/test/general/bug792517.sjs create mode 100644 browser/base/content/test/general/bug839103.css create mode 100644 browser/base/content/test/general/clipboard_pastefile.html create mode 100644 browser/base/content/test/general/close_beforeunload.html create mode 100644 browser/base/content/test/general/close_beforeunload_opens_second_tab.html create mode 100644 browser/base/content/test/general/contentSearchUI.html create mode 100644 browser/base/content/test/general/contentSearchUI.js create mode 100644 browser/base/content/test/general/content_aboutAccounts.js create mode 100644 browser/base/content/test/general/contextmenu_common.js create mode 100644 browser/base/content/test/general/ctxmenu-image.png create mode 100644 browser/base/content/test/general/discovery.html create mode 100644 browser/base/content/test/general/download_page.html create mode 100644 browser/base/content/test/general/dummy_page.html create mode 100644 browser/base/content/test/general/feed_discovery.html create mode 100644 browser/base/content/test/general/feed_tab.html create mode 100644 browser/base/content/test/general/file_bug1045809_1.html create mode 100644 browser/base/content/test/general/file_bug1045809_2.html create mode 100644 browser/base/content/test/general/file_bug822367_1.html create mode 100644 browser/base/content/test/general/file_bug822367_1.js create mode 100644 browser/base/content/test/general/file_bug822367_2.html create mode 100644 browser/base/content/test/general/file_bug822367_3.html create mode 100644 browser/base/content/test/general/file_bug822367_4.html create mode 100644 browser/base/content/test/general/file_bug822367_4.js create mode 100644 browser/base/content/test/general/file_bug822367_4B.html create mode 100644 browser/base/content/test/general/file_bug822367_5.html create mode 100644 browser/base/content/test/general/file_bug822367_6.html create mode 100644 browser/base/content/test/general/file_bug902156.js create mode 100644 browser/base/content/test/general/file_bug902156_1.html create mode 100644 browser/base/content/test/general/file_bug902156_2.html create mode 100644 browser/base/content/test/general/file_bug902156_3.html create mode 100644 browser/base/content/test/general/file_bug906190.js create mode 100644 browser/base/content/test/general/file_bug906190.sjs create mode 100644 browser/base/content/test/general/file_bug906190_1.html create mode 100644 browser/base/content/test/general/file_bug906190_2.html create mode 100644 browser/base/content/test/general/file_bug906190_3_4.html create mode 100644 browser/base/content/test/general/file_bug906190_redirected.html create mode 100644 browser/base/content/test/general/file_bug970276_favicon1.ico create mode 100644 browser/base/content/test/general/file_bug970276_favicon2.ico create mode 100644 browser/base/content/test/general/file_bug970276_popup1.html create mode 100644 browser/base/content/test/general/file_bug970276_popup2.html create mode 100644 browser/base/content/test/general/file_csp_block_all_mixedcontent.html create mode 100644 browser/base/content/test/general/file_csp_block_all_mixedcontent.js create mode 100644 browser/base/content/test/general/file_documentnavigation_frameset.html create mode 100644 browser/base/content/test/general/file_double_close_tab.html create mode 100644 browser/base/content/test/general/file_favicon_change.html create mode 100644 browser/base/content/test/general/file_favicon_change_not_in_document.html create mode 100644 browser/base/content/test/general/file_fullscreen-window-open.html create mode 100644 browser/base/content/test/general/file_generic_favicon.ico create mode 100644 browser/base/content/test/general/file_mediaPlayback.html create mode 100644 browser/base/content/test/general/file_mixedContentFramesOnHttp.html create mode 100644 browser/base/content/test/general/file_mixedContentFromOnunload.html create mode 100644 browser/base/content/test/general/file_mixedContentFromOnunload_test1.html create mode 100644 browser/base/content/test/general/file_mixedContentFromOnunload_test2.html create mode 100644 browser/base/content/test/general/file_mixedPassiveContent.html create mode 100644 browser/base/content/test/general/file_trackingUI_6.html create mode 100644 browser/base/content/test/general/file_trackingUI_6.js create mode 100644 browser/base/content/test/general/file_trackingUI_6.js^headers^ create mode 100644 browser/base/content/test/general/file_with_favicon.html create mode 100644 browser/base/content/test/general/fxa_profile_handler.sjs create mode 100644 browser/base/content/test/general/gZipOfflineChild.cacheManifest create mode 100644 browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^ create mode 100644 browser/base/content/test/general/gZipOfflineChild.html create mode 100644 browser/base/content/test/general/gZipOfflineChild.html^headers^ create mode 100644 browser/base/content/test/general/gZipOfflineChild_uncompressed.html create mode 100644 browser/base/content/test/general/head.js create mode 100644 browser/base/content/test/general/head_plain.js create mode 100644 browser/base/content/test/general/healthreport_pingData.js create mode 100644 browser/base/content/test/general/healthreport_testRemoteCommands.html create mode 100644 browser/base/content/test/general/insecure_opener.html create mode 100644 browser/base/content/test/general/mochitest.ini create mode 100644 browser/base/content/test/general/moz.png create mode 100644 browser/base/content/test/general/navigating_window_with_download.html create mode 100644 browser/base/content/test/general/offlineByDefault.js create mode 100644 browser/base/content/test/general/offlineChild.cacheManifest create mode 100644 browser/base/content/test/general/offlineChild.cacheManifest^headers^ create mode 100644 browser/base/content/test/general/offlineChild.html create mode 100644 browser/base/content/test/general/offlineChild2.cacheManifest create mode 100644 browser/base/content/test/general/offlineChild2.cacheManifest^headers^ create mode 100644 browser/base/content/test/general/offlineChild2.html create mode 100644 browser/base/content/test/general/offlineEvent.cacheManifest create mode 100644 browser/base/content/test/general/offlineEvent.cacheManifest^headers^ create mode 100644 browser/base/content/test/general/offlineEvent.html create mode 100644 browser/base/content/test/general/offlineQuotaNotification.cacheManifest create mode 100644 browser/base/content/test/general/offlineQuotaNotification.html create mode 100644 browser/base/content/test/general/page_style_sample.html create mode 100644 browser/base/content/test/general/parsingTestHelpers.jsm create mode 100644 browser/base/content/test/general/permissions.html create mode 100644 browser/base/content/test/general/pinning_headers.sjs create mode 100644 browser/base/content/test/general/print_postdata.sjs create mode 100644 browser/base/content/test/general/refresh_header.sjs create mode 100644 browser/base/content/test/general/refresh_meta.sjs create mode 100644 browser/base/content/test/general/searchSuggestionEngine.sjs create mode 100644 browser/base/content/test/general/searchSuggestionEngine.xml create mode 100644 browser/base/content/test/general/searchSuggestionEngine2.xml create mode 100644 browser/base/content/test/general/ssl_error_reports.sjs create mode 100644 browser/base/content/test/general/subtst_contextmenu.html create mode 100644 browser/base/content/test/general/subtst_contextmenu_input.html create mode 100644 browser/base/content/test/general/subtst_contextmenu_xul.xul create mode 100644 browser/base/content/test/general/svg_image.html create mode 100644 browser/base/content/test/general/test-mixedcontent-securityerrors.html create mode 100644 browser/base/content/test/general/test_bug364677.html create mode 100644 browser/base/content/test/general/test_bug395533.html create mode 100644 browser/base/content/test/general/test_bug435035.html create mode 100644 browser/base/content/test/general/test_bug462673.html create mode 100644 browser/base/content/test/general/test_bug628179.html create mode 100644 browser/base/content/test/general/test_bug839103.html create mode 100644 browser/base/content/test/general/test_bug959531.html create mode 100644 browser/base/content/test/general/test_mcb_double_redirect_image.html create mode 100644 browser/base/content/test/general/test_mcb_redirect.html create mode 100644 browser/base/content/test/general/test_mcb_redirect.js create mode 100644 browser/base/content/test/general/test_mcb_redirect.sjs create mode 100644 browser/base/content/test/general/test_mcb_redirect_image.html create mode 100644 browser/base/content/test/general/test_no_mcb_on_http_site_font.css create mode 100644 browser/base/content/test/general/test_no_mcb_on_http_site_font.html create mode 100644 browser/base/content/test/general/test_no_mcb_on_http_site_font2.css create mode 100644 browser/base/content/test/general/test_no_mcb_on_http_site_font2.html create mode 100644 browser/base/content/test/general/test_no_mcb_on_http_site_img.css create mode 100644 browser/base/content/test/general/test_no_mcb_on_http_site_img.html create mode 100644 browser/base/content/test/general/test_offlineNotification.html create mode 100644 browser/base/content/test/general/test_offline_gzip.html create mode 100644 browser/base/content/test/general/test_process_flags_chrome.html create mode 100644 browser/base/content/test/general/test_remoteTroubleshoot.html create mode 100644 browser/base/content/test/general/title_test.svg create mode 100644 browser/base/content/test/general/trackingPage.html create mode 100644 browser/base/content/test/general/unknownContentType_file.pif create mode 100644 browser/base/content/test/general/unknownContentType_file.pif^headers^ create mode 100644 browser/base/content/test/general/video.ogg create mode 100644 browser/base/content/test/general/web_video.html create mode 100644 browser/base/content/test/general/web_video1.ogv create mode 100644 browser/base/content/test/general/web_video1.ogv^headers^ create mode 100644 browser/base/content/test/general/zoom_test.html (limited to 'browser/base/content/test/general') 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 @@ + + POST Search + + + + 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 @@ + + + + + + + + + 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 @@ + + +Test page for bug 463387 + + +

Test page for bug 463387

+ + 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 @@ + + + + + Test for links in app tabs + + + same domain + same domain (different subdomain) + different domain + different domain (with target) + same domain (www prefix) + data: URI + + + 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 @@ + + + + + Test for links in app tab subframes + + + different domain + + 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 Binary files /dev/null and b/browser/base/content/test/general/audio.ogg 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 @@ + + + + + + + + + + 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", "

"), + 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,

" + + "" + + "Checkbox" + + "

"; + 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," + + "" + + ""; + 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,test%20data:%20url', + '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,' + + ''; + 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,' + + ''; + let testPage2 = + 'data:text/html,' + + ''; + 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,' + + ''; + + 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,' + + '
Bold Textitalics
'; + + 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, "Bold Textitalics", + "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,OK"]; + +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,Content!Link'; + 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, "; + 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,'; + +// 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,' + + '"; +var testPage2 = "data:text/html,"; +var testPage3 = "data:text/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 = "" + value + ""; + 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,"); + + 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,"+ + 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 + + + + + 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 @@ + + + + + fxa_oauth_test + + + + + 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 @@ + + + + + fxa_web_channel_test + + + + + 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(//g); + if (!entities) { + // Some files, such as requestAutocomplete.dtd, have no entities defined. + return; + } + for (let entity of entities) { + let [, key, str] = entity.match(//); + // 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,Middle-click me"; + +/* + * 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,Click me`; + +/** + * 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 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 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=" 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=" + + " 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 = "http://www.example.com/example"; + 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 () + * 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 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 @@ + + + + Protocol registrar page + + + + + + + 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 + * + */ +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