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/components/sessionstore/test/.eslintrc.js | 7 + browser/components/sessionstore/test/browser.ini | 242 +++++++++ .../sessionstore/test/browser_1234021.js | 18 + .../sessionstore/test/browser_1234021_page.html | 6 + .../test/browser_248970_b_perwindowpb.js | 166 ++++++ .../sessionstore/test/browser_248970_b_sample.html | 37 ++ .../components/sessionstore/test/browser_339445.js | 32 ++ .../sessionstore/test/browser_339445_sample.html | 18 + .../components/sessionstore/test/browser_345898.js | 44 ++ .../components/sessionstore/test/browser_350525.js | 102 ++++ .../test/browser_354894_perwindowpb.js | 474 +++++++++++++++++ .../components/sessionstore/test/browser_367052.js | 41 ++ .../components/sessionstore/test/browser_393716.js | 71 +++ .../sessionstore/test/browser_394759_basic.js | 92 ++++ .../sessionstore/test/browser_394759_behavior.js | 76 +++ .../test/browser_394759_perwindowpb.js | 55 ++ .../sessionstore/test/browser_394759_purge.js | 130 +++++ .../components/sessionstore/test/browser_423132.js | 59 +++ .../sessionstore/test/browser_423132_sample.html | 14 + .../components/sessionstore/test/browser_447951.js | 65 +++ .../sessionstore/test/browser_447951_sample.html | 5 + .../components/sessionstore/test/browser_454908.js | 47 ++ .../sessionstore/test/browser_454908_sample.html | 8 + .../components/sessionstore/test/browser_456342.js | 49 ++ .../sessionstore/test/browser_456342_sample.xhtml | 36 ++ .../components/sessionstore/test/browser_459906.js | 62 +++ .../sessionstore/test/browser_459906_empty.html | 3 + .../sessionstore/test/browser_459906_sample.html | 41 ++ .../components/sessionstore/test/browser_461634.js | 85 ++++ .../components/sessionstore/test/browser_461743.js | 39 ++ .../sessionstore/test/browser_461743_sample.html | 56 ++ .../components/sessionstore/test/browser_463205.js | 40 ++ .../sessionstore/test/browser_463205_sample.html | 7 + .../components/sessionstore/test/browser_463206.js | 53 ++ .../sessionstore/test/browser_463206_sample.html | 11 + .../components/sessionstore/test/browser_464199.js | 85 ++++ .../sessionstore/test/browser_464620_a.html | 54 ++ .../sessionstore/test/browser_464620_a.js | 48 ++ .../sessionstore/test/browser_464620_b.html | 58 +++ .../sessionstore/test/browser_464620_b.js | 48 ++ .../sessionstore/test/browser_464620_xd.html | 5 + .../components/sessionstore/test/browser_465215.js | 28 + .../components/sessionstore/test/browser_465223.js | 45 ++ .../components/sessionstore/test/browser_466937.js | 42 ++ .../sessionstore/test/browser_466937_sample.html | 22 + .../test/browser_467409-backslashplosion.js | 74 +++ .../components/sessionstore/test/browser_477657.js | 60 +++ .../components/sessionstore/test/browser_480893.js | 47 ++ .../components/sessionstore/test/browser_485482.js | 37 ++ .../sessionstore/test/browser_485482_sample.html | 12 + .../components/sessionstore/test/browser_485563.js | 26 + .../components/sessionstore/test/browser_490040.js | 65 +++ .../components/sessionstore/test/browser_491168.js | 42 ++ .../components/sessionstore/test/browser_491577.js | 120 +++++ .../components/sessionstore/test/browser_495495.js | 46 ++ .../components/sessionstore/test/browser_500328.js | 120 +++++ .../components/sessionstore/test/browser_506482.js | 73 +++ .../components/sessionstore/test/browser_514751.js | 38 ++ .../components/sessionstore/test/browser_522375.js | 21 + .../components/sessionstore/test/browser_522545.js | 269 ++++++++++ .../components/sessionstore/test/browser_524745.js | 42 ++ .../components/sessionstore/test/browser_526613.js | 72 +++ .../components/sessionstore/test/browser_528776.js | 21 + .../components/sessionstore/test/browser_579868.js | 30 ++ .../components/sessionstore/test/browser_579879.js | 20 + .../components/sessionstore/test/browser_580512.js | 81 +++ .../components/sessionstore/test/browser_581937.js | 19 + .../sessionstore/test/browser_586068-apptabs.js | 58 +++ .../test/browser_586068-apptabs_ondemand.js | 53 ++ .../browser_586068-browser_state_interrupted.js | 113 +++++ .../sessionstore/test/browser_586068-cascade.js | 54 ++ .../test/browser_586068-multi_window.js | 70 +++ .../sessionstore/test/browser_586068-reload.js | 54 ++ .../sessionstore/test/browser_586068-select.js | 69 +++ .../test/browser_586068-window_state.js | 59 +++ .../test/browser_586068-window_state_override.js | 59 +++ .../components/sessionstore/test/browser_586147.js | 52 ++ .../components/sessionstore/test/browser_588426.js | 41 ++ .../components/sessionstore/test/browser_589246.js | 242 +++++++++ .../components/sessionstore/test/browser_590268.js | 137 +++++ .../components/sessionstore/test/browser_590563.js | 74 +++ .../test/browser_595601-restore_hidden.js | 112 ++++ .../components/sessionstore/test/browser_597071.js | 36 ++ .../components/sessionstore/test/browser_599909.js | 120 +++++ .../components/sessionstore/test/browser_600545.js | 89 ++++ .../components/sessionstore/test/browser_601955.js | 54 ++ .../components/sessionstore/test/browser_607016.js | 98 ++++ .../test/browser_615394-SSWindowState_events.js | 361 +++++++++++++ .../components/sessionstore/test/browser_618151.js | 65 +++ .../components/sessionstore/test/browser_623779.js | 13 + .../components/sessionstore/test/browser_624727.js | 35 ++ .../components/sessionstore/test/browser_625016.js | 82 +++ .../components/sessionstore/test/browser_628270.js | 52 ++ .../components/sessionstore/test/browser_635418.js | 55 ++ .../components/sessionstore/test/browser_636279.js | 101 ++++ .../components/sessionstore/test/browser_637020.js | 66 +++ .../sessionstore/test/browser_637020_slow.sjs | 21 + .../test/browser_644409-scratchpads.js | 68 +++ .../components/sessionstore/test/browser_645428.js | 22 + .../components/sessionstore/test/browser_659591.js | 33 ++ .../components/sessionstore/test/browser_662743.js | 110 ++++ .../sessionstore/test/browser_662743_sample.html | 15 + .../components/sessionstore/test/browser_662812.js | 36 ++ .../test/browser_665702-state_session.js | 24 + .../components/sessionstore/test/browser_682507.js | 16 + .../components/sessionstore/test/browser_687710.js | 44 ++ .../sessionstore/test/browser_687710_2.js | 64 +++ .../components/sessionstore/test/browser_694378.js | 33 ++ .../components/sessionstore/test/browser_701377.js | 41 ++ .../components/sessionstore/test/browser_705597.js | 58 +++ .../components/sessionstore/test/browser_707862.js | 61 +++ .../components/sessionstore/test/browser_739531.js | 47 ++ .../sessionstore/test/browser_739531_sample.html | 25 + .../components/sessionstore/test/browser_739805.js | 41 ++ .../test/browser_819510_perwindowpb.js | 120 +++++ .../components/sessionstore/test/browser_911547.js | 63 +++ .../sessionstore/test/browser_911547_sample.html | 19 + .../test/browser_911547_sample.html^headers^ | 1 + .../test/browser_aboutPrivateBrowsing.js | 21 + .../test/browser_aboutSessionRestore.js | 55 ++ .../test/browser_async_duplicate_tab.js | 78 +++ .../sessionstore/test/browser_async_flushes.js | 113 +++++ .../sessionstore/test/browser_async_remove_tab.js | 242 +++++++++ .../test/browser_async_window_flushing.js | 178 +++++++ .../sessionstore/test/browser_attributes.js | 73 +++ .../test/browser_background_tab_crash.js | 221 ++++++++ .../sessionstore/test/browser_backup_recovery.js | 206 ++++++++ .../sessionstore/test/browser_broadcast.js | 131 +++++ .../sessionstore/test/browser_capabilities.js | 76 +++ .../sessionstore/test/browser_cleaner.js | 157 ++++++ .../sessionstore/test/browser_cookies.js | 173 +++++++ .../sessionstore/test/browser_cookies.sjs | 21 + .../sessionstore/test/browser_crashedTabs.js | 462 +++++++++++++++++ .../sessionstore/test/browser_dying_cache.js | 66 +++ .../sessionstore/test/browser_dynamic_frames.js | 87 ++++ .../test/browser_forget_async_closings.js | 144 ++++++ .../test/browser_form_restore_events.js | 63 +++ .../test/browser_form_restore_events_sample.html | 99 ++++ .../sessionstore/test/browser_formdata.js | 194 +++++++ .../sessionstore/test/browser_formdata_cc.js | 79 +++ .../sessionstore/test/browser_formdata_format.js | 113 +++++ .../test/browser_formdata_format_sample.html | 7 + .../sessionstore/test/browser_formdata_sample.html | 20 + .../sessionstore/test/browser_formdata_xpath.js | 151 ++++++ .../test/browser_formdata_xpath_sample.html | 37 ++ .../sessionstore/test/browser_frame_history.js | 170 +++++++ .../sessionstore/test/browser_frame_history_a.html | 5 + .../sessionstore/test/browser_frame_history_b.html | 10 + .../sessionstore/test/browser_frame_history_c.html | 5 + .../test/browser_frame_history_c1.html | 5 + .../test/browser_frame_history_c2.html | 5 + .../test/browser_frame_history_index.html | 10 + .../test/browser_frame_history_index2.html | 4 + .../test/browser_frame_history_index_blank.html | 5 + .../sessionstore/test/browser_frametree.js | 131 +++++ .../test/browser_frametree_sample.html | 8 + .../test/browser_frametree_sample_frameset.html | 11 + .../sessionstore/test/browser_global_store.js | 45 ++ .../sessionstore/test/browser_history_persist.js | 93 ++++ .../sessionstore/test/browser_label_and_icon.js | 53 ++ .../sessionstore/test/browser_merge_closed_tabs.js | 71 +++ .../test/browser_multiple_navigateAndRestore.js | 36 ++ .../test/browser_newtab_userTypedValue.js | 72 +++ .../sessionstore/test/browser_pageStyle.js | 89 ++++ .../test/browser_pageStyle_sample.html | 16 + .../test/browser_pageStyle_sample_nested.html | 9 + .../sessionstore/test/browser_page_title.js | 45 ++ .../test/browser_parentProcessRestoreHash.js | 95 ++++ .../sessionstore/test/browser_pending_tabs.js | 35 ++ .../sessionstore/test/browser_privatetabs.js | 133 +++++ .../sessionstore/test/browser_purge_shistory.js | 59 +++ .../test/browser_remoteness_flip_on_restore.js | 342 +++++++++++++ .../sessionstore/test/browser_replace_load.js | 52 ++ .../browser_restore_cookies_noOriginAttributes.js | 171 +++++++ .../sessionstore/test/browser_restore_redirect.js | 69 +++ .../test/browser_revive_crashed_bg_tabs.js | 56 ++ .../sessionstore/test/browser_scrollPositions.js | 153 ++++++ .../test/browser_scrollPositionsReaderMode.js | 67 +++ .../browser_scrollPositions_readerModeArticle.html | 26 + .../test/browser_scrollPositions_sample.html | 8 + .../browser_scrollPositions_sample_frameset.html | 11 + .../test/browser_send_async_message_oom.js | 75 +++ .../sessionstore/test/browser_sessionHistory.js | 240 +++++++++ .../test/browser_sessionHistory_slow.sjs | 21 + .../sessionstore/test/browser_sessionStorage.html | 27 + .../sessionstore/test/browser_sessionStorage.js | 188 +++++++ .../test/browser_sessionStorage_size.js | 51 ++ .../test/browser_sessionStoreContainer.js | 141 ++++++ .../sessionstore/test/browser_swapDocShells.js | 35 ++ .../sessionstore/test/browser_switch_remoteness.js | 49 ++ .../sessionstore/test/browser_undoCloseById.js | 118 +++++ .../test/browser_unrestored_crashedTabs.js | 69 +++ .../sessionstore/test/browser_upgrade_backup.js | 134 +++++ .../test/browser_windowRestore_perwindowpb.js | 26 + .../test/browser_windowStateContainer.js | 122 +++++ .../components/sessionstore/test/content-forms.js | 133 +++++ browser/components/sessionstore/test/content.js | 222 ++++++++ browser/components/sessionstore/test/head.js | 564 +++++++++++++++++++++ .../sessionstore/test/restore_redirect_http.html | 0 .../test/restore_redirect_http.html^headers^ | 2 + .../sessionstore/test/restore_redirect_js.html | 10 + .../sessionstore/test/restore_redirect_target.html | 8 + .../components/sessionstore/test/unit/.eslintrc.js | 7 + .../test/unit/data/sessionCheckpoints_all.json | 1 + .../test/unit/data/sessionstore_invalid.js | 3 + .../test/unit/data/sessionstore_valid.js | 3 + browser/components/sessionstore/test/unit/head.js | 32 ++ .../sessionstore/test/unit/test_backup_once.js | 130 +++++ .../test/unit/test_histogram_corrupt_files.js | 114 +++++ .../test/unit/test_shutdown_cleanup.js | 127 +++++ .../test/unit/test_startup_invalid_session.js | 21 + .../test/unit/test_startup_nosession_async.js | 22 + .../test/unit/test_startup_session_async.js | 27 + .../components/sessionstore/test/unit/xpcshell.ini | 16 + 214 files changed, 15677 insertions(+) create mode 100644 browser/components/sessionstore/test/.eslintrc.js create mode 100644 browser/components/sessionstore/test/browser.ini create mode 100644 browser/components/sessionstore/test/browser_1234021.js create mode 100644 browser/components/sessionstore/test/browser_1234021_page.html create mode 100644 browser/components/sessionstore/test/browser_248970_b_perwindowpb.js create mode 100644 browser/components/sessionstore/test/browser_248970_b_sample.html create mode 100644 browser/components/sessionstore/test/browser_339445.js create mode 100644 browser/components/sessionstore/test/browser_339445_sample.html create mode 100644 browser/components/sessionstore/test/browser_345898.js create mode 100644 browser/components/sessionstore/test/browser_350525.js create mode 100644 browser/components/sessionstore/test/browser_354894_perwindowpb.js create mode 100644 browser/components/sessionstore/test/browser_367052.js create mode 100644 browser/components/sessionstore/test/browser_393716.js create mode 100644 browser/components/sessionstore/test/browser_394759_basic.js create mode 100644 browser/components/sessionstore/test/browser_394759_behavior.js create mode 100644 browser/components/sessionstore/test/browser_394759_perwindowpb.js create mode 100644 browser/components/sessionstore/test/browser_394759_purge.js create mode 100644 browser/components/sessionstore/test/browser_423132.js create mode 100644 browser/components/sessionstore/test/browser_423132_sample.html create mode 100644 browser/components/sessionstore/test/browser_447951.js create mode 100644 browser/components/sessionstore/test/browser_447951_sample.html create mode 100644 browser/components/sessionstore/test/browser_454908.js create mode 100644 browser/components/sessionstore/test/browser_454908_sample.html create mode 100644 browser/components/sessionstore/test/browser_456342.js create mode 100644 browser/components/sessionstore/test/browser_456342_sample.xhtml create mode 100644 browser/components/sessionstore/test/browser_459906.js create mode 100644 browser/components/sessionstore/test/browser_459906_empty.html create mode 100644 browser/components/sessionstore/test/browser_459906_sample.html create mode 100644 browser/components/sessionstore/test/browser_461634.js create mode 100644 browser/components/sessionstore/test/browser_461743.js create mode 100644 browser/components/sessionstore/test/browser_461743_sample.html create mode 100644 browser/components/sessionstore/test/browser_463205.js create mode 100644 browser/components/sessionstore/test/browser_463205_sample.html create mode 100644 browser/components/sessionstore/test/browser_463206.js create mode 100644 browser/components/sessionstore/test/browser_463206_sample.html create mode 100644 browser/components/sessionstore/test/browser_464199.js create mode 100644 browser/components/sessionstore/test/browser_464620_a.html create mode 100644 browser/components/sessionstore/test/browser_464620_a.js create mode 100644 browser/components/sessionstore/test/browser_464620_b.html create mode 100644 browser/components/sessionstore/test/browser_464620_b.js create mode 100644 browser/components/sessionstore/test/browser_464620_xd.html create mode 100644 browser/components/sessionstore/test/browser_465215.js create mode 100644 browser/components/sessionstore/test/browser_465223.js create mode 100644 browser/components/sessionstore/test/browser_466937.js create mode 100644 browser/components/sessionstore/test/browser_466937_sample.html create mode 100644 browser/components/sessionstore/test/browser_467409-backslashplosion.js create mode 100644 browser/components/sessionstore/test/browser_477657.js create mode 100644 browser/components/sessionstore/test/browser_480893.js create mode 100644 browser/components/sessionstore/test/browser_485482.js create mode 100644 browser/components/sessionstore/test/browser_485482_sample.html create mode 100644 browser/components/sessionstore/test/browser_485563.js create mode 100644 browser/components/sessionstore/test/browser_490040.js create mode 100644 browser/components/sessionstore/test/browser_491168.js create mode 100644 browser/components/sessionstore/test/browser_491577.js create mode 100644 browser/components/sessionstore/test/browser_495495.js create mode 100644 browser/components/sessionstore/test/browser_500328.js create mode 100644 browser/components/sessionstore/test/browser_506482.js create mode 100644 browser/components/sessionstore/test/browser_514751.js create mode 100644 browser/components/sessionstore/test/browser_522375.js create mode 100644 browser/components/sessionstore/test/browser_522545.js create mode 100644 browser/components/sessionstore/test/browser_524745.js create mode 100644 browser/components/sessionstore/test/browser_526613.js create mode 100644 browser/components/sessionstore/test/browser_528776.js create mode 100644 browser/components/sessionstore/test/browser_579868.js create mode 100644 browser/components/sessionstore/test/browser_579879.js create mode 100644 browser/components/sessionstore/test/browser_580512.js create mode 100644 browser/components/sessionstore/test/browser_581937.js create mode 100644 browser/components/sessionstore/test/browser_586068-apptabs.js create mode 100644 browser/components/sessionstore/test/browser_586068-apptabs_ondemand.js create mode 100644 browser/components/sessionstore/test/browser_586068-browser_state_interrupted.js create mode 100644 browser/components/sessionstore/test/browser_586068-cascade.js create mode 100644 browser/components/sessionstore/test/browser_586068-multi_window.js create mode 100644 browser/components/sessionstore/test/browser_586068-reload.js create mode 100644 browser/components/sessionstore/test/browser_586068-select.js create mode 100644 browser/components/sessionstore/test/browser_586068-window_state.js create mode 100644 browser/components/sessionstore/test/browser_586068-window_state_override.js create mode 100644 browser/components/sessionstore/test/browser_586147.js create mode 100644 browser/components/sessionstore/test/browser_588426.js create mode 100644 browser/components/sessionstore/test/browser_589246.js create mode 100644 browser/components/sessionstore/test/browser_590268.js create mode 100644 browser/components/sessionstore/test/browser_590563.js create mode 100644 browser/components/sessionstore/test/browser_595601-restore_hidden.js create mode 100644 browser/components/sessionstore/test/browser_597071.js create mode 100644 browser/components/sessionstore/test/browser_599909.js create mode 100644 browser/components/sessionstore/test/browser_600545.js create mode 100644 browser/components/sessionstore/test/browser_601955.js create mode 100644 browser/components/sessionstore/test/browser_607016.js create mode 100644 browser/components/sessionstore/test/browser_615394-SSWindowState_events.js create mode 100644 browser/components/sessionstore/test/browser_618151.js create mode 100644 browser/components/sessionstore/test/browser_623779.js create mode 100644 browser/components/sessionstore/test/browser_624727.js create mode 100644 browser/components/sessionstore/test/browser_625016.js create mode 100644 browser/components/sessionstore/test/browser_628270.js create mode 100644 browser/components/sessionstore/test/browser_635418.js create mode 100644 browser/components/sessionstore/test/browser_636279.js create mode 100644 browser/components/sessionstore/test/browser_637020.js create mode 100644 browser/components/sessionstore/test/browser_637020_slow.sjs create mode 100644 browser/components/sessionstore/test/browser_644409-scratchpads.js create mode 100644 browser/components/sessionstore/test/browser_645428.js create mode 100644 browser/components/sessionstore/test/browser_659591.js create mode 100644 browser/components/sessionstore/test/browser_662743.js create mode 100644 browser/components/sessionstore/test/browser_662743_sample.html create mode 100644 browser/components/sessionstore/test/browser_662812.js create mode 100644 browser/components/sessionstore/test/browser_665702-state_session.js create mode 100644 browser/components/sessionstore/test/browser_682507.js create mode 100644 browser/components/sessionstore/test/browser_687710.js create mode 100644 browser/components/sessionstore/test/browser_687710_2.js create mode 100644 browser/components/sessionstore/test/browser_694378.js create mode 100644 browser/components/sessionstore/test/browser_701377.js create mode 100644 browser/components/sessionstore/test/browser_705597.js create mode 100644 browser/components/sessionstore/test/browser_707862.js create mode 100644 browser/components/sessionstore/test/browser_739531.js create mode 100644 browser/components/sessionstore/test/browser_739531_sample.html create mode 100644 browser/components/sessionstore/test/browser_739805.js create mode 100644 browser/components/sessionstore/test/browser_819510_perwindowpb.js create mode 100644 browser/components/sessionstore/test/browser_911547.js create mode 100644 browser/components/sessionstore/test/browser_911547_sample.html create mode 100644 browser/components/sessionstore/test/browser_911547_sample.html^headers^ create mode 100644 browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js create mode 100644 browser/components/sessionstore/test/browser_aboutSessionRestore.js create mode 100644 browser/components/sessionstore/test/browser_async_duplicate_tab.js create mode 100644 browser/components/sessionstore/test/browser_async_flushes.js create mode 100644 browser/components/sessionstore/test/browser_async_remove_tab.js create mode 100644 browser/components/sessionstore/test/browser_async_window_flushing.js create mode 100644 browser/components/sessionstore/test/browser_attributes.js create mode 100644 browser/components/sessionstore/test/browser_background_tab_crash.js create mode 100644 browser/components/sessionstore/test/browser_backup_recovery.js create mode 100644 browser/components/sessionstore/test/browser_broadcast.js create mode 100644 browser/components/sessionstore/test/browser_capabilities.js create mode 100644 browser/components/sessionstore/test/browser_cleaner.js create mode 100644 browser/components/sessionstore/test/browser_cookies.js create mode 100644 browser/components/sessionstore/test/browser_cookies.sjs create mode 100644 browser/components/sessionstore/test/browser_crashedTabs.js create mode 100644 browser/components/sessionstore/test/browser_dying_cache.js create mode 100644 browser/components/sessionstore/test/browser_dynamic_frames.js create mode 100644 browser/components/sessionstore/test/browser_forget_async_closings.js create mode 100644 browser/components/sessionstore/test/browser_form_restore_events.js create mode 100644 browser/components/sessionstore/test/browser_form_restore_events_sample.html create mode 100644 browser/components/sessionstore/test/browser_formdata.js create mode 100644 browser/components/sessionstore/test/browser_formdata_cc.js create mode 100644 browser/components/sessionstore/test/browser_formdata_format.js create mode 100644 browser/components/sessionstore/test/browser_formdata_format_sample.html create mode 100644 browser/components/sessionstore/test/browser_formdata_sample.html create mode 100644 browser/components/sessionstore/test/browser_formdata_xpath.js create mode 100644 browser/components/sessionstore/test/browser_formdata_xpath_sample.html create mode 100644 browser/components/sessionstore/test/browser_frame_history.js create mode 100755 browser/components/sessionstore/test/browser_frame_history_a.html create mode 100755 browser/components/sessionstore/test/browser_frame_history_b.html create mode 100755 browser/components/sessionstore/test/browser_frame_history_c.html create mode 100755 browser/components/sessionstore/test/browser_frame_history_c1.html create mode 100755 browser/components/sessionstore/test/browser_frame_history_c2.html create mode 100644 browser/components/sessionstore/test/browser_frame_history_index.html create mode 100644 browser/components/sessionstore/test/browser_frame_history_index2.html create mode 100644 browser/components/sessionstore/test/browser_frame_history_index_blank.html create mode 100644 browser/components/sessionstore/test/browser_frametree.js create mode 100644 browser/components/sessionstore/test/browser_frametree_sample.html create mode 100644 browser/components/sessionstore/test/browser_frametree_sample_frameset.html create mode 100644 browser/components/sessionstore/test/browser_global_store.js create mode 100644 browser/components/sessionstore/test/browser_history_persist.js create mode 100644 browser/components/sessionstore/test/browser_label_and_icon.js create mode 100644 browser/components/sessionstore/test/browser_merge_closed_tabs.js create mode 100644 browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js create mode 100644 browser/components/sessionstore/test/browser_newtab_userTypedValue.js create mode 100644 browser/components/sessionstore/test/browser_pageStyle.js create mode 100644 browser/components/sessionstore/test/browser_pageStyle_sample.html create mode 100644 browser/components/sessionstore/test/browser_pageStyle_sample_nested.html create mode 100644 browser/components/sessionstore/test/browser_page_title.js create mode 100644 browser/components/sessionstore/test/browser_parentProcessRestoreHash.js create mode 100644 browser/components/sessionstore/test/browser_pending_tabs.js create mode 100644 browser/components/sessionstore/test/browser_privatetabs.js create mode 100644 browser/components/sessionstore/test/browser_purge_shistory.js create mode 100644 browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js create mode 100644 browser/components/sessionstore/test/browser_replace_load.js create mode 100644 browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js create mode 100644 browser/components/sessionstore/test/browser_restore_redirect.js create mode 100644 browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js create mode 100644 browser/components/sessionstore/test/browser_scrollPositions.js create mode 100644 browser/components/sessionstore/test/browser_scrollPositionsReaderMode.js create mode 100644 browser/components/sessionstore/test/browser_scrollPositions_readerModeArticle.html create mode 100644 browser/components/sessionstore/test/browser_scrollPositions_sample.html create mode 100644 browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html create mode 100644 browser/components/sessionstore/test/browser_send_async_message_oom.js create mode 100644 browser/components/sessionstore/test/browser_sessionHistory.js create mode 100644 browser/components/sessionstore/test/browser_sessionHistory_slow.sjs create mode 100644 browser/components/sessionstore/test/browser_sessionStorage.html create mode 100644 browser/components/sessionstore/test/browser_sessionStorage.js create mode 100644 browser/components/sessionstore/test/browser_sessionStorage_size.js create mode 100644 browser/components/sessionstore/test/browser_sessionStoreContainer.js create mode 100644 browser/components/sessionstore/test/browser_swapDocShells.js create mode 100644 browser/components/sessionstore/test/browser_switch_remoteness.js create mode 100644 browser/components/sessionstore/test/browser_undoCloseById.js create mode 100644 browser/components/sessionstore/test/browser_unrestored_crashedTabs.js create mode 100644 browser/components/sessionstore/test/browser_upgrade_backup.js create mode 100644 browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js create mode 100644 browser/components/sessionstore/test/browser_windowStateContainer.js create mode 100644 browser/components/sessionstore/test/content-forms.js create mode 100644 browser/components/sessionstore/test/content.js create mode 100644 browser/components/sessionstore/test/head.js create mode 100644 browser/components/sessionstore/test/restore_redirect_http.html create mode 100644 browser/components/sessionstore/test/restore_redirect_http.html^headers^ create mode 100644 browser/components/sessionstore/test/restore_redirect_js.html create mode 100644 browser/components/sessionstore/test/restore_redirect_target.html create mode 100644 browser/components/sessionstore/test/unit/.eslintrc.js create mode 100644 browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json create mode 100644 browser/components/sessionstore/test/unit/data/sessionstore_invalid.js create mode 100644 browser/components/sessionstore/test/unit/data/sessionstore_valid.js create mode 100644 browser/components/sessionstore/test/unit/head.js create mode 100644 browser/components/sessionstore/test/unit/test_backup_once.js create mode 100644 browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js create mode 100644 browser/components/sessionstore/test/unit/test_shutdown_cleanup.js create mode 100644 browser/components/sessionstore/test/unit/test_startup_invalid_session.js create mode 100644 browser/components/sessionstore/test/unit/test_startup_nosession_async.js create mode 100644 browser/components/sessionstore/test/unit/test_startup_session_async.js create mode 100644 browser/components/sessionstore/test/unit/xpcshell.ini (limited to 'browser/components/sessionstore/test') diff --git a/browser/components/sessionstore/test/.eslintrc.js b/browser/components/sessionstore/test/.eslintrc.js new file mode 100644 index 000000000..c764b133d --- /dev/null +++ b/browser/components/sessionstore/test/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini new file mode 100644 index 000000000..37154a0cc --- /dev/null +++ b/browser/components/sessionstore/test/browser.ini @@ -0,0 +1,242 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# browser_506482.js is disabled because of frequent failures (bug 538672) +# browser_526613.js is disabled because of frequent failures (bug 534489) +# browser_589246.js is disabled for leaking browser windows (bug 752467) +# browser_580512.js is disabled for leaking browser windows (bug 752467) + +[DEFAULT] +support-files = + head.js + content.js + content-forms.js + browser_cookies.sjs + browser_formdata_sample.html + browser_formdata_xpath_sample.html + browser_frametree_sample.html + browser_frametree_sample_frameset.html + browser_frame_history_index.html + browser_frame_history_index2.html + browser_frame_history_index_blank.html + browser_frame_history_a.html + browser_frame_history_b.html + browser_frame_history_c.html + browser_frame_history_c1.html + browser_frame_history_c2.html + browser_form_restore_events_sample.html + browser_formdata_format_sample.html + browser_pageStyle_sample.html + browser_pageStyle_sample_nested.html + browser_sessionHistory_slow.sjs + browser_scrollPositions_sample.html + browser_scrollPositions_sample_frameset.html + browser_scrollPositions_readerModeArticle.html + browser_sessionStorage.html + browser_248970_b_sample.html + browser_339445_sample.html + browser_423132_sample.html + browser_447951_sample.html + browser_454908_sample.html + browser_456342_sample.xhtml + browser_463205_sample.html + browser_463206_sample.html + browser_466937_sample.html + browser_485482_sample.html + browser_637020_slow.sjs + browser_662743_sample.html + browser_739531_sample.html + browser_911547_sample.html + browser_911547_sample.html^headers^ + restore_redirect_http.html + restore_redirect_http.html^headers^ + restore_redirect_js.html + restore_redirect_target.html + browser_1234021_page.html + +#NB: the following are disabled +# browser_464620_a.html +# browser_464620_b.html +# browser_464620_xd.html + + +#disabled-for-intermittent-failures--bug-766044, browser_459906_empty.html +#disabled-for-intermittent-failures--bug-766044, browser_459906_sample.html +#disabled-for-intermittent-failures--bug-765389, browser_461743_sample.html + +[browser_aboutPrivateBrowsing.js] +[browser_aboutSessionRestore.js] +[browser_async_duplicate_tab.js] +[browser_async_flushes.js] +run-if = e10s && crashreporter +skip-if = debug # bug 1167933 +[browser_async_remove_tab.js] +run-if = e10s +skip-if = debug # bug 1211084 +[browser_attributes.js] +[browser_backup_recovery.js] +[browser_broadcast.js] +[browser_capabilities.js] +[browser_cleaner.js] +[browser_cookies.js] +[browser_crashedTabs.js] +skip-if = !e10s || !crashreporter +[browser_unrestored_crashedTabs.js] +skip-if = !e10s || !crashreporter +[browser_revive_crashed_bg_tabs.js] +skip-if = !e10s || !crashreporter +[browser_dying_cache.js] +[browser_dynamic_frames.js] +[browser_form_restore_events.js] +[browser_formdata.js] +[browser_formdata_cc.js] +[browser_formdata_format.js] +[browser_formdata_xpath.js] +[browser_frametree.js] +[browser_frame_history.js] +[browser_global_store.js] +[browser_history_persist.js] +[browser_label_and_icon.js] +[browser_merge_closed_tabs.js] +[browser_page_title.js] +[browser_pageStyle.js] +[browser_pending_tabs.js] +[browser_privatetabs.js] +[browser_purge_shistory.js] +skip-if = e10s # Bug 1271024 +[browser_replace_load.js] +[browser_restore_redirect.js] +[browser_restore_cookies_noOriginAttributes.js] +[browser_scrollPositions.js] +[browser_scrollPositionsReaderMode.js] +[browser_sessionHistory.js] +[browser_sessionStorage.js] +[browser_sessionStorage_size.js] +[browser_swapDocShells.js] +[browser_switch_remoteness.js] +run-if = e10s +[browser_upgrade_backup.js] +[browser_windowRestore_perwindowpb.js] +[browser_248970_b_perwindowpb.js] +# Disabled because of leaks. +# Re-enabling and rewriting this test is tracked in bug 936919. +skip-if = true +[browser_339445.js] +[browser_345898.js] +[browser_350525.js] +[browser_354894_perwindowpb.js] +[browser_367052.js] +[browser_393716.js] +[browser_394759_basic.js] +# Disabled for intermittent failures, bug 944372. +skip-if = true +[browser_394759_behavior.js] +[browser_394759_perwindowpb.js] +[browser_394759_purge.js] +[browser_423132.js] +[browser_447951.js] +[browser_454908.js] +[browser_456342.js] +[browser_461634.js] +[browser_463205.js] +[browser_463206.js] +[browser_464199.js] +[browser_465215.js] +[browser_465223.js] +[browser_466937.js] +[browser_467409-backslashplosion.js] +[browser_477657.js] +[browser_480893.js] +[browser_485482.js] +[browser_485563.js] +[browser_490040.js] +[browser_491168.js] +[browser_491577.js] +[browser_495495.js] +[browser_500328.js] +[browser_514751.js] +[browser_522375.js] +[browser_522545.js] +[browser_524745.js] +[browser_528776.js] +[browser_579868.js] +[browser_579879.js] +skip-if = (os == 'linux' && e10s && (debug||asan)) # Bug 1234404 +[browser_581937.js] +[browser_586147.js] +[browser_586068-apptabs.js] +[browser_586068-apptabs_ondemand.js] +[browser_586068-browser_state_interrupted.js] +[browser_586068-cascade.js] +[browser_586068-multi_window.js] +[browser_586068-reload.js] +[browser_586068-select.js] +[browser_586068-window_state.js] +[browser_586068-window_state_override.js] +[browser_588426.js] +[browser_590268.js] +[browser_590563.js] +[browser_595601-restore_hidden.js] +[browser_597071.js] +skip-if = true # Needs to be rewritten as Marionette test, bug 995916 +[browser_599909.js] +[browser_600545.js] +[browser_601955.js] +[browser_607016.js] +[browser_615394-SSWindowState_events.js] +[browser_618151.js] +[browser_623779.js] +[browser_624727.js] +[browser_628270.js] +[browser_635418.js] +[browser_636279.js] +[browser_637020.js] +[browser_644409-scratchpads.js] +[browser_645428.js] +[browser_659591.js] +[browser_662743.js] +[browser_662812.js] +[browser_665702-state_session.js] +[browser_682507.js] +[browser_687710.js] +[browser_687710_2.js] +[browser_694378.js] +[browser_701377.js] +[browser_705597.js] +[browser_707862.js] +[browser_739531.js] +[browser_739805.js] +[browser_819510_perwindowpb.js] +skip-if = (os == 'win' && bits == 64) # Bug 1284312 + +# Disabled for frequent intermittent failures +[browser_464620_a.js] +skip-if = true +[browser_464620_b.js] +skip-if = true + +# Disabled on OS X: +[browser_625016.js] +skip-if = os == "mac" + +[browser_911547.js] +[browser_send_async_message_oom.js] +[browser_multiple_navigateAndRestore.js] +run-if = e10s +[browser_async_window_flushing.js] +[browser_forget_async_closings.js] +[browser_newtab_userTypedValue.js] +[browser_parentProcessRestoreHash.js] +run-if = e10s +[browser_sessionStoreContainer.js] +[browser_windowStateContainer.js] +[browser_1234021.js] +[browser_remoteness_flip_on_restore.js] +run-if = e10s +[browser_background_tab_crash.js] +run-if = e10s && crashreporter + +# Disabled on debug for frequent intermittent failures: +[browser_undoCloseById.js] +skip-if = debug diff --git a/browser/components/sessionstore/test/browser_1234021.js b/browser/components/sessionstore/test/browser_1234021.js new file mode 100644 index 000000000..a307d1e01 --- /dev/null +++ b/browser/components/sessionstore/test/browser_1234021.js @@ -0,0 +1,18 @@ +"use strict"; + +const PREF = 'network.cookie.cookieBehavior'; +const PAGE_URL = 'http://mochi.test:8888/browser/' + + 'browser/components/sessionstore/test/browser_1234021_page.html'; +const BEHAVIOR_REJECT = 2; + +add_task(function* test() { + yield pushPrefs([PREF, BEHAVIOR_REJECT]); + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser, + url: PAGE_URL + }, function* handler(aBrowser) { + yield TabStateFlusher.flush(aBrowser); + ok(true, "Flush didn't time out"); + }); +}); diff --git a/browser/components/sessionstore/test/browser_1234021_page.html b/browser/components/sessionstore/test/browser_1234021_page.html new file mode 100644 index 000000000..4a74fbc02 --- /dev/null +++ b/browser/components/sessionstore/test/browser_1234021_page.html @@ -0,0 +1,6 @@ + + + + diff --git a/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js b/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js new file mode 100644 index 000000000..f5775cd5b --- /dev/null +++ b/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() { + /** Test (B) for Bug 248970 **/ + waitForExplicitFinish(); + + let windowsToClose = []; + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); + let filePath = file.path; + let fieldList = { + "//input[@name='input']": Date.now().toString(), + "//input[@name='spaced 1']": Math.random().toString(), + "//input[3]": "three", + "//input[@type='checkbox']": true, + "//input[@name='uncheck']": false, + "//input[@type='radio'][1]": false, + "//input[@type='radio'][2]": true, + "//input[@type='radio'][3]": false, + "//select": 2, + "//select[@multiple]": [1, 3], + "//textarea[1]": "", + "//textarea[2]": "Some text... " + Math.random(), + "//textarea[3]": "Some more text\n" + new Date(), + "//input[@type='file']": filePath + }; + + registerCleanupFunction(function* () { + for (let win of windowsToClose) { + yield BrowserTestUtils.closeWindow(win); + } + }); + + function test(aLambda) { + try { + return aLambda() || true; + } catch(ex) { } + return false; + } + + function getElementByXPath(aTab, aQuery) { + let doc = aTab.linkedBrowser.contentDocument; + let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE; + return doc.evaluate(aQuery, doc, null, xptype, null).singleNodeValue; + } + + function setFormValue(aTab, aQuery, aValue) { + let node = getElementByXPath(aTab, aQuery); + if (typeof aValue == "string") + node.value = aValue; + else if (typeof aValue == "boolean") + node.checked = aValue; + else if (typeof aValue == "number") + node.selectedIndex = aValue; + else + Array.forEach(node.options, (aOpt, aIx) => + (aOpt.selected = aValue.indexOf(aIx) > -1)); + } + + function compareFormValue(aTab, aQuery, aValue) { + let node = getElementByXPath(aTab, aQuery); + if (!node) + return false; + if (node instanceof Ci.nsIDOMHTMLInputElement) + return aValue == (node.type == "checkbox" || node.type == "radio" ? + node.checked : node.value); + if (node instanceof Ci.nsIDOMHTMLTextAreaElement) + return aValue == node.value; + if (!node.multiple) + return aValue == node.selectedIndex; + return Array.every(node.options, (aOpt, aIx) => + (aValue.indexOf(aIx) > -1) == aOpt.selected); + } + + ////////////////////////////////////////////////////////////////// + // Test (B) : Session data restoration between windows // + ////////////////////////////////////////////////////////////////// + + let rootDir = getRootDirectory(gTestPath); + const testURL = rootDir + "browser_248970_b_sample.html"; + const testURL2 = "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_248970_b_sample.html"; + + whenNewWindowLoaded({ private: false }, function(aWin) { + windowsToClose.push(aWin); + + // get closed tab count + let count = ss.getClosedTabCount(aWin); + let max_tabs_undo = + Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo"); + ok(0 <= count && count <= max_tabs_undo, + "getClosedTabCount should return zero or at most max_tabs_undo"); + + // setup a state for tab (A) so we can check later that is restored + let key = "key"; + let value = "Value " + Math.random(); + let state = { entries: [{ url: testURL }], extData: { key: value } }; + + // public session, add new tab: (A) + let tab_A = aWin.gBrowser.addTab(testURL); + ss.setTabState(tab_A, JSON.stringify(state)); + promiseBrowserLoaded(tab_A.linkedBrowser).then(() => { + // make sure that the next closed tab will increase getClosedTabCount + Services.prefs.setIntPref( + "browser.sessionstore.max_tabs_undo", max_tabs_undo + 1) + + // populate tab_A with form data + for (let i in fieldList) + setFormValue(tab_A, i, fieldList[i]); + + // public session, close tab: (A) + aWin.gBrowser.removeTab(tab_A); + + // verify that closedTabCount increased + ok(ss.getClosedTabCount(aWin) > count, + "getClosedTabCount has increased after closing a tab"); + + // verify tab: (A), in undo list + let tab_A_restored = test(() => ss.undoCloseTab(aWin, 0)); + ok(tab_A_restored, "a tab is in undo list"); + promiseTabRestored(tab_A_restored).then(() => { + is(testURL, tab_A_restored.linkedBrowser.currentURI.spec, + "it's the same tab that we expect"); + aWin.gBrowser.removeTab(tab_A_restored); + + whenNewWindowLoaded({ private: true }, function(aWin) { + windowsToClose.push(aWin); + + // setup a state for tab (B) so we can check that its duplicated + // properly + let key1 = "key1"; + let value1 = "Value " + Math.random(); + let state1 = { + entries: [{ url: testURL2 }], extData: { key1: value1 } + }; + + let tab_B = aWin.gBrowser.addTab(testURL2); + promiseTabState(tab_B, state1).then(() => { + // populate tab: (B) with different form data + for (let item in fieldList) + setFormValue(tab_B, item, fieldList[item]); + + // duplicate tab: (B) + let tab_C = aWin.gBrowser.duplicateTab(tab_B); + promiseTabRestored(tab_C).then(() => { + // verify the correctness of the duplicated tab + is(ss.getTabValue(tab_C, key1), value1, + "tab successfully duplicated - correct state"); + + for (let item in fieldList) + ok(compareFormValue(tab_C, item, fieldList[item]), + "The value for \"" + item + "\" was correctly duplicated"); + + // private browsing session, close tab: (C) and (B) + aWin.gBrowser.removeTab(tab_C); + aWin.gBrowser.removeTab(tab_B); + + finish(); + }); + }); + }); + }); + }); + }); +} diff --git a/browser/components/sessionstore/test/browser_248970_b_sample.html b/browser/components/sessionstore/test/browser_248970_b_sample.html new file mode 100644 index 000000000..76c3ae1aa --- /dev/null +++ b/browser/components/sessionstore/test/browser_248970_b_sample.html @@ -0,0 +1,37 @@ + + +Test for bug 248970 + +

Text Fields

+ + + + +

Checkboxes and Radio buttons

+ Check 1 + Check 2 +

+ Radio 1 + Radio 2 + Radio 3 + +

Selects

+ + + +

Text Areas

+ + + + +

File Selector

+ diff --git a/browser/components/sessionstore/test/browser_339445.js b/browser/components/sessionstore/test/browser_339445.js new file mode 100644 index 000000000..c38a6cb18 --- /dev/null +++ b/browser/components/sessionstore/test/browser_339445.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* test() { + /** Test for Bug 339445 **/ + + let testURL = "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_339445_sample.html"; + + let tab = gBrowser.addTab(testURL); + yield promiseBrowserLoaded(tab.linkedBrowser); + + yield ContentTask.spawn(tab.linkedBrowser, null, function() { + let doc = content.document; + is(doc.getElementById("storageTestItem").textContent, "PENDING", + "sessionStorage value has been set"); + }); + + let tab2 = gBrowser.duplicateTab(tab); + yield promiseTabRestored(tab2); + + yield ContentTask.spawn(tab2.linkedBrowser, null, function() { + let doc2 = content.document; + is(doc2.getElementById("storageTestItem").textContent, "SUCCESS", + "sessionStorage value has been duplicated"); + }); + + // clean up + yield Promise.all([ BrowserTestUtils.removeTab(tab2), + BrowserTestUtils.removeTab(tab) ]); +}); diff --git a/browser/components/sessionstore/test/browser_339445_sample.html b/browser/components/sessionstore/test/browser_339445_sample.html new file mode 100644 index 000000000..32656a8d9 --- /dev/null +++ b/browser/components/sessionstore/test/browser_339445_sample.html @@ -0,0 +1,18 @@ + + +Test for bug 339445 + +storageTestItem = FAIL + + + + diff --git a/browser/components/sessionstore/test/browser_345898.js b/browser/components/sessionstore/test/browser_345898.js new file mode 100644 index 000000000..bd4a46e69 --- /dev/null +++ b/browser/components/sessionstore/test/browser_345898.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() { + /** Test for Bug 345898 **/ + + function test(aLambda) { + try { + aLambda(); + return false; + } + catch (ex) { + return ex.name == "NS_ERROR_ILLEGAL_VALUE" || + ex.name == "NS_ERROR_FAILURE"; + } + } + + // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE + ok(test(() => ss.getWindowState({})), + "Invalid window for getWindowState throws"); + ok(test(() => ss.setWindowState({}, "", false)), + "Invalid window for setWindowState throws"); + ok(test(() => ss.getTabState({})), + "Invalid tab for getTabState throws"); + ok(test(() => ss.setTabState({}, "{}")), + "Invalid tab state for setTabState throws"); + ok(test(() => ss.setTabState({}, JSON.stringify({ entries: [] }))), + "Invalid tab for setTabState throws"); + ok(test(() => ss.duplicateTab({}, {})), + "Invalid tab for duplicateTab throws"); + ok(test(() => ss.duplicateTab({}, gBrowser.selectedTab)), + "Invalid window for duplicateTab throws"); + ok(test(() => ss.getClosedTabData({})), + "Invalid window for getClosedTabData throws"); + ok(test(() => ss.undoCloseTab({}, 0)), + "Invalid window for undoCloseTab throws"); + ok(test(() => ss.undoCloseTab(window, -1)), + "Invalid index for undoCloseTab throws"); + ok(test(() => ss.getWindowValue({}, "")), + "Invalid window for getWindowValue throws"); + ok(test(() => ss.setWindowValue({}, "", "")), + "Invalid window for setWindowValue throws"); +} diff --git a/browser/components/sessionstore/test/browser_350525.js b/browser/components/sessionstore/test/browser_350525.js new file mode 100644 index 000000000..1d87b3754 --- /dev/null +++ b/browser/components/sessionstore/test/browser_350525.js @@ -0,0 +1,102 @@ +"use strict"; + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]] + }); +}) + +add_task(function* () { + /** Test for Bug 350525 **/ + + function test(aLambda) { + try { + return aLambda() || true; + } + catch (ex) { } + return false; + } + + //////////////////////////// + // setWindowValue, et al. // + //////////////////////////// + let key = "Unique name: " + Date.now(); + let value = "Unique value: " + Math.random(); + + // test adding + ok(test(() => ss.setWindowValue(window, key, value)), "set a window value"); + + // test retrieving + is(ss.getWindowValue(window, key), value, "stored window value matches original"); + + // test deleting + ok(test(() => ss.deleteWindowValue(window, key)), "delete the window value"); + + // value should not exist post-delete + is(ss.getWindowValue(window, key), "", "window value was deleted"); + + // test deleting a non-existent value + ok(test(() => ss.deleteWindowValue(window, key)), "delete non-existent window value"); + + ///////////////////////// + // setTabValue, et al. // + ///////////////////////// + key = "Unique name: " + Math.random(); + value = "Unique value: " + Date.now(); + let tab = gBrowser.addTab(); + tab.linkedBrowser.stop(); + + // test adding + ok(test(() => ss.setTabValue(tab, key, value)), "store a tab value"); + + // test retrieving + is(ss.getTabValue(tab, key), value, "stored tab value match original"); + + // test deleting + ok(test(() => ss.deleteTabValue(tab, key)), "delete the tab value"); + + // value should not exist post-delete + is(ss.getTabValue(tab, key), "", "tab value was deleted"); + + // test deleting a non-existent value + ok(test(() => ss.deleteTabValue(tab, key)), "delete non-existent tab value"); + + // clean up + yield promiseRemoveTab(tab); + + ///////////////////////////////////// + // getClosedTabCount, undoCloseTab // + ///////////////////////////////////// + + // get closed tab count + let count = ss.getClosedTabCount(window); + let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo"); + ok(0 <= count && count <= max_tabs_undo, + "getClosedTabCount returns zero or at most max_tabs_undo"); + + // create a new tab + let testURL = "about:"; + tab = gBrowser.addTab(testURL); + yield promiseBrowserLoaded(tab.linkedBrowser); + + // make sure that the next closed tab will increase getClosedTabCount + gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1); + registerCleanupFunction(() => gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo")); + + // remove tab + yield promiseRemoveTab(tab); + + // getClosedTabCount + let newcount = ss.getClosedTabCount(window); + ok(newcount > count, "after closing a tab, getClosedTabCount has been incremented"); + + // undoCloseTab + tab = test(() => ss.undoCloseTab(window, 0)); + ok(tab, "undoCloseTab doesn't throw") + + yield promiseTabRestored(tab); + is(tab.linkedBrowser.currentURI.spec, testURL, "correct tab was reopened"); + + // clean up + gBrowser.removeTab(tab); +}); diff --git a/browser/components/sessionstore/test/browser_354894_perwindowpb.js b/browser/components/sessionstore/test/browser_354894_perwindowpb.js new file mode 100644 index 000000000..bf80cd710 --- /dev/null +++ b/browser/components/sessionstore/test/browser_354894_perwindowpb.js @@ -0,0 +1,474 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Checks that restoring the last browser window in session is actually + * working. + * + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=354894 + * @note It is implicitly tested that restoring the last window works when + * non-browser windows are around. The "Run Tests" window as well as the main + * browser window (wherein the test code gets executed) won't be considered + * browser windows. To achiveve this said main browser window has it's windowtype + * attribute modified so that it's not considered a browser window any longer. + * This is crucial, because otherwise there would be two browser windows around, + * said main test window and the one opened by the tests, and hence the new + * logic wouldn't be executed at all. + * @note Mac only tests the new notifications, as restoring the last window is + * not enabled on that platform (platform shim; the application is kept running + * although there are no windows left) + * @note There is a difference when closing a browser window with + * BrowserTryToCloseWindow() as opposed to close(). The former will make + * nsSessionStore restore a window next time it gets a chance and will post + * notifications. The latter won't. + */ + +// Some urls that might be opened in tabs and/or popups +// Do not use about:blank: +// That one is reserved for special purposes in the tests +const TEST_URLS = ["about:mozilla", "about:buildconfig"]; + +// Number of -request notifications to except +// remember to adjust when adding new tests +const NOTIFICATIONS_EXPECTED = 6; + +// Window features of popup windows +const POPUP_FEATURES = "toolbar=no,resizable=no,status=no"; + +// Window features of browser windows +const CHROME_FEATURES = "chrome,all,dialog=no"; + +const IS_MAC = navigator.platform.match(/Mac/); + +/** + * Returns an Object with two properties: + * open (int): + * A count of how many non-closed navigator:browser windows there are. + * winstates (int): + * A count of how many windows there are in the SessionStore state. + */ +function getBrowserWindowsCount() { + let open = 0; + let e = Services.wm.getEnumerator("navigator:browser"); + while (e.hasMoreElements()) { + if (!e.getNext().closed) + ++open; + } + + let winstates = JSON.parse(ss.getBrowserState()).windows.length; + + return { open, winstates }; +} + +add_task(function* setup() { + // Make sure we've only got one browser window to start with + let { open, winstates } = getBrowserWindowsCount(); + is(open, 1, "Should only be one open window"); + is(winstates, 1, "Should only be one window state in SessionStore"); + + // This test takes some time to run, and it could timeout randomly. + // So we require a longer timeout. See bug 528219. + requestLongerTimeout(3); + + // Make the main test window not count as a browser window any longer + let oldWinType = document.documentElement.getAttribute("windowtype"); + document.documentElement.setAttribute("windowtype", "navigator:testrunner"); + + registerCleanupFunction(() => { + document.documentElement.setAttribute("windowtype", "navigator:browser"); + }); +}); + +/** + * Sets up one of our tests by setting the right preferences, and + * then opening up a browser window preloaded with some tabs. + * + * @param options (Object) + * An object that can contain the following properties: + * + * private: + * Whether or not the opened window should be private. + * + * denyFirst: + * Whether or not the first window that attempts to close + * via closeWindowForRestoration should be denied. + * + * @param testFunction (Function*) + * A generator function that yields Promises to be run + * once the test has been set up. + * + * @returns Promise + * Resolves once the test has been cleaned up. + */ +let setupTest = Task.async(function*(options, testFunction) { + yield pushPrefs(["browser.startup.page", 3], + ["browser.tabs.warnOnClose", false]); + + // Observe these, and also use to count the number of hits + let observing = { + "browser-lastwindow-close-requested": 0, + "browser-lastwindow-close-granted": 0 + }; + + /** + * Helper: Will observe and handle the notifications for us + */ + let hitCount = 0; + function observer(aCancel, aTopic, aData) { + // count so that we later may compare + observing[aTopic]++; + + // handle some tests + if (options.denyFirst && ++hitCount == 1) { + aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true; + } + } + + for (let o in observing) { + Services.obs.addObserver(observer, o, false); + } + + let private = options.private || false; + let newWin = yield promiseNewWindowLoaded({ private }); + + injectTestTabs(newWin); + + yield testFunction(newWin, observing); + + let count = getBrowserWindowsCount(); + is(count.open, 0, "Got right number of open windows"); + is(count.winstates, 1, "Got right number of stored window states"); + + for (let o in observing) { + Services.obs.removeObserver(observer, o); + } + + yield popPrefs(); +}); + +/** + * Loads a TEST_URLS into a browser window. + * + * @param win (Window) + * The browser window to load the tabs in + */ +function injectTestTabs(win) { + TEST_URLS.forEach(function (url) { + win.gBrowser.addTab(url); + }); +} + +/** + * Attempts to close a window via BrowserTryToCloseWindow so that + * we get the browser-lastwindow-close-requested and + * browser-lastwindow-close-granted observer notifications. + * + * @param win (Window) + * The window to try to close + * @returns Promise + * Resolves to true if the window closed, or false if the window + * was denied the ability to close. + */ +function closeWindowForRestoration(win) { + return new Promise((resolve) => { + let closePromise = BrowserTestUtils.windowClosed(win); + win.BrowserTryToCloseWindow(); + if (!win.closed) { + resolve(false); + return; + } + + closePromise.then(() => { + resolve(true); + }); + }); +} + +/** + * Normal in-session restore + * + * @note: Non-Mac only + * + * Should do the following: + * 1. Open a new browser window + * 2. Add some tabs + * 3. Close that window + * 4. Opening another window + * 5. Checks that state is restored + */ +add_task(function* test_open_close_normal() { + if (IS_MAC) { + return; + } + + yield setupTest({ denyFirst: true }, function*(newWin, obs) { + let closed = yield closeWindowForRestoration(newWin); + ok(!closed, "First close request should have been denied"); + + closed = yield closeWindowForRestoration(newWin); + ok(closed, "Second close request should be accepted"); + + newWin = yield promiseNewWindowLoaded(); + is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2, + "Restored window in-session with otherpopup windows around"); + + // Note that this will not result in the the browser-lastwindow-close + // notifications firing for this other newWin. + yield BrowserTestUtils.closeWindow(newWin); + + // setupTest gave us a window which was denied for closing once, and then + // closed. + is(obs["browser-lastwindow-close-requested"], 2, + "Got expected browser-lastwindow-close-requested notifications"); + is(obs["browser-lastwindow-close-granted"], 1, + "Got expected browser-lastwindow-close-granted notifications"); + }); +}); + +/** + * PrivateBrowsing in-session restore + * + * @note: Non-Mac only + * + * Should do the following: + * 1. Open a new browser window A + * 2. Add some tabs + * 3. Close the window A as the last window + * 4. Open a private browsing window B + * 5. Make sure that B didn't restore the tabs from A + * 6. Close private browsing window B + * 7. Open a new window C + * 8. Make sure that new window C has restored tabs from A + */ +add_task(function* test_open_close_private_browsing() { + if (IS_MAC) { + return; + } + + yield setupTest({}, function*(newWin, obs) { + let closed = yield closeWindowForRestoration(newWin); + ok(closed, "Should be able to close the window"); + + newWin = yield promiseNewWindowLoaded({private: true}); + is(newWin.gBrowser.browsers.length, 1, + "Did not restore in private browing mode"); + + closed = yield closeWindowForRestoration(newWin); + ok(closed, "Should be able to close the window"); + + newWin = yield promiseNewWindowLoaded(); + is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2, + "Restored tabs in a new non-private window"); + + // Note that this will not result in the the browser-lastwindow-close + // notifications firing for this other newWin. + yield BrowserTestUtils.closeWindow(newWin); + + // We closed two windows with closeWindowForRestoration, and both + // should have been successful. + is(obs["browser-lastwindow-close-requested"], 2, + "Got expected browser-lastwindow-close-requested notifications"); + is(obs["browser-lastwindow-close-granted"], 2, + "Got expected browser-lastwindow-close-granted notifications"); + }); +}); + +/** + * Open some popup windows to check those aren't restored, but the browser + * window is. + * + * @note: Non-Mac only + * + * Should do the following: + * 1. Open a new browser window + * 2. Add some tabs + * 3. Open some popups + * 4. Add another tab to one popup (so that it gets stored) and close it again + * 5. Close the browser window + * 6. Open another browser window + * 7. Make sure that the tabs of the closed browser window, but not the popup, + * are restored + */ +add_task(function* test_open_close_window_and_popup() { + if (IS_MAC) { + return; + } + + yield setupTest({}, function*(newWin, obs) { + let popupPromise = BrowserTestUtils.waitForNewWindow(); + openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[0]); + let popup = yield popupPromise; + + let popup2Promise = BrowserTestUtils.waitForNewWindow(); + openDialog(location, "popup2", POPUP_FEATURES, TEST_URLS[1]); + let popup2 = yield popup2Promise; + + popup2.gBrowser.addTab(TEST_URLS[0]); + + let closed = yield closeWindowForRestoration(newWin); + ok(closed, "Should be able to close the window"); + + yield BrowserTestUtils.closeWindow(popup2); + + newWin = yield promiseNewWindowLoaded(); + + is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2, + "Restored window and associated tabs in session"); + + yield BrowserTestUtils.closeWindow(popup); + yield BrowserTestUtils.closeWindow(newWin); + + // We closed one window with closeWindowForRestoration, and it should + // have been successful. + is(obs["browser-lastwindow-close-requested"], 1, + "Got expected browser-lastwindow-close-requested notifications"); + is(obs["browser-lastwindow-close-granted"], 1, + "Got expected browser-lastwindow-close-granted notifications"); + }); +}); + +/** + * Open some popup window to check it isn't restored. Instead nothing at all + * should be restored + * + * @note: Non-Mac only + * + * Should do the following: + * 1. Open a popup + * 2. Add another tab to the popup (so that it gets stored) and close it again + * 3. Open a window + * 4. Check that nothing at all is restored + * 5. Open two browser windows and close them again + * 6. undoCloseWindow() one + * 7. Open another browser window + * 8. Check that nothing at all is restored + */ +add_task(function* test_open_close_only_popup() { + if (IS_MAC) { + return; + } + + yield setupTest({}, function*(newWin, obs) { + // We actually don't care about the initial window in this test. + yield BrowserTestUtils.closeWindow(newWin); + + // This will cause nsSessionStore to restore a window the next time it + // gets a chance. + let popupPromise = BrowserTestUtils.waitForNewWindow(); + openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]); + let popup = yield popupPromise; + + is(popup.gBrowser.browsers.length, 1, + "Did not restore the popup window (1)"); + + let closed = yield closeWindowForRestoration(popup); + ok(closed, "Should be able to close the window"); + + popupPromise = BrowserTestUtils.waitForNewWindow(); + openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]); + popup = yield popupPromise; + + popup.gBrowser.addTab(TEST_URLS[0]); + is(popup.gBrowser.browsers.length, 2, + "Did not restore to the popup window (2)"); + + yield BrowserTestUtils.closeWindow(popup); + + newWin = yield promiseNewWindowLoaded(); + isnot(newWin.gBrowser.browsers.length, 2, + "Did not restore the popup window"); + is(TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), -1, + "Did not restore the popup window (2)"); + yield BrowserTestUtils.closeWindow(newWin); + + // We closed one popup window with closeWindowForRestoration, and popup + // windows should never fire the browser-lastwindow notifications. + is(obs["browser-lastwindow-close-requested"], 0, + "Got expected browser-lastwindow-close-requested notifications"); + is(obs["browser-lastwindow-close-granted"], 0, + "Got expected browser-lastwindow-close-granted notifications"); + }); +}); + +/** + * Open some windows and do undoCloseWindow. This should prevent any + * restoring later in the test + * + * @note: Non-Mac only + * + * Should do the following: + * 1. Open two browser windows and close them again + * 2. undoCloseWindow() one + * 3. Open another browser window + * 4. Make sure nothing at all is restored + */ +add_task(function* test_open_close_restore_from_popup() { + if (IS_MAC) { + return; + } + + yield setupTest({}, function*(newWin, obs) { + let newWin2 = yield promiseNewWindowLoaded(); + yield injectTestTabs(newWin2); + + let closed = yield closeWindowForRestoration(newWin); + ok(closed, "Should be able to close the window"); + closed = yield closeWindowForRestoration(newWin2); + ok(closed, "Should be able to close the window"); + + let counts = getBrowserWindowsCount(); + is(counts.open, 0, "Got right number of open windows"); + is(counts.winstates, 1, "Got right number of window states"); + + newWin = undoCloseWindow(0); + yield BrowserTestUtils.waitForEvent(newWin, "load"); + + // Make sure we wait until this window is restored. + yield BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer, + "SSTabRestored"); + + newWin2 = yield promiseNewWindowLoaded(); + + is(newWin2.gBrowser.browsers.length, 1, + "Did not restore, as undoCloseWindow() was last called"); + is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1, + "Did not restore, as undoCloseWindow() was last called (2)"); + + counts = getBrowserWindowsCount(); + is(counts.open, 2, "Got right number of open windows"); + is(counts.winstates, 3, "Got right number of window states"); + + yield BrowserTestUtils.closeWindow(newWin); + yield BrowserTestUtils.closeWindow(newWin2); + + counts = getBrowserWindowsCount(); + is(counts.open, 0, "Got right number of open windows"); + is(counts.winstates, 1, "Got right number of window states"); + }); +}); + +/** + * Test if closing can be denied on Mac. + * @note: Mac only + */ +add_task(function* test_mac_notifications() { + if (!IS_MAC) { + return; + } + + yield setupTest({ denyFirst: true }, function*(newWin, obs) { + let closed = yield closeWindowForRestoration(newWin); + ok(!closed, "First close attempt should be denied"); + closed = yield closeWindowForRestoration(newWin); + ok(closed, "Second close attempt should be granted"); + + // We tried closing once, and got denied. Then we tried again and + // succeeded. That means 2 close requests, and 1 close granted. + is(obs["browser-lastwindow-close-requested"], 2, + "Got expected browser-lastwindow-close-requested notifications"); + is(obs["browser-lastwindow-close-granted"], 1, + "Got expected browser-lastwindow-close-granted notifications"); + }); +}); + diff --git a/browser/components/sessionstore/test/browser_367052.js b/browser/components/sessionstore/test/browser_367052.js new file mode 100644 index 000000000..3cc89a66c --- /dev/null +++ b/browser/components/sessionstore/test/browser_367052.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function* () { + // make sure that the next closed tab will increase getClosedTabCount + let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo"); + gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1); + registerCleanupFunction(() => gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo")); + + // Empty the list of closed tabs. + while (ss.getClosedTabCount(window)) { + ss.forgetClosedTab(window, 0); + } + + // restore a blank tab + let tab = gBrowser.addTab("about:"); + yield promiseBrowserLoaded(tab.linkedBrowser); + + let count = yield promiseSHistoryCount(tab.linkedBrowser); + ok(count >= 1, "the new tab does have at least one history entry"); + + yield promiseTabState(tab, {entries: []}); + + // We may have a different sessionHistory object if the tab + // switched from non-remote to remote. + count = yield promiseSHistoryCount(tab.linkedBrowser); + is(count, 0, "the tab was restored without any history whatsoever"); + + yield promiseRemoveTab(tab); + is(ss.getClosedTabCount(window), 0, + "The closed blank tab wasn't added to Recently Closed Tabs"); +}); + +function promiseSHistoryCount(browser) { + return ContentTask.spawn(browser, null, function* () { + return docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.count; + }); +} diff --git a/browser/components/sessionstore/test/browser_393716.js b/browser/components/sessionstore/test/browser_393716.js new file mode 100644 index 000000000..c59bdcc8b --- /dev/null +++ b/browser/components/sessionstore/test/browser_393716.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const URL = "about:config"; + +/** + * Bug 393716 - Basic tests for getTabState(), setTabState(), and duplicateTab(). + */ +add_task(function test_set_tabstate() { + let key = "Unique key: " + Date.now(); + let value = "Unique value: " + Math.random(); + + // create a new tab + let tab = gBrowser.addTab(URL); + ss.setTabValue(tab, key, value); + yield promiseBrowserLoaded(tab.linkedBrowser); + + // get the tab's state + yield TabStateFlusher.flush(tab.linkedBrowser); + let state = ss.getTabState(tab); + ok(state, "get the tab's state"); + + // verify the tab state's integrity + state = JSON.parse(state); + ok(state instanceof Object && state.entries instanceof Array && state.entries.length > 0, + "state object seems valid"); + ok(state.entries.length == 1 && state.entries[0].url == URL, + "Got the expected state object (test URL)"); + ok(state.extData && state.extData[key] == value, + "Got the expected state object (test manually set tab value)"); + + // clean up + gBrowser.removeTab(tab); +}); + +add_task(function test_set_tabstate_and_duplicate() { + let key2 = "key2"; + let value2 = "Value " + Math.random(); + let value3 = "Another value: " + Date.now(); + let state = { entries: [{ url: URL }], extData: { key2: value2 } }; + + // create a new tab + let tab = gBrowser.addTab(); + // set the tab's state + ss.setTabState(tab, JSON.stringify(state)); + yield promiseBrowserLoaded(tab.linkedBrowser); + + // verify the correctness of the restored tab + ok(ss.getTabValue(tab, key2) == value2 && tab.linkedBrowser.currentURI.spec == URL, + "the tab's state was correctly restored"); + + // add text data + yield setInputValue(tab.linkedBrowser, {id: "textbox", value: value3}); + + // duplicate the tab + let tab2 = ss.duplicateTab(window, tab); + yield promiseTabRestored(tab2); + + // verify the correctness of the duplicated tab + ok(ss.getTabValue(tab2, key2) == value2 && + tab2.linkedBrowser.currentURI.spec == URL, + "correctly duplicated the tab's state"); + let textbox = yield getInputValue(tab2.linkedBrowser, {id: "textbox"}); + is(textbox, value3, "also duplicated text data"); + + // clean up + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab); +}); diff --git a/browser/components/sessionstore/test/browser_394759_basic.js b/browser/components/sessionstore/test/browser_394759_basic.js new file mode 100644 index 000000000..1b1650e27 --- /dev/null +++ b/browser/components/sessionstore/test/browser_394759_basic.js @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_URL = "data:text/html;charset=utf-8," + + ""; + +Cu.import("resource:///modules/sessionstore/SessionStore.jsm"); + +/** + * This test ensures that closing a window is a reversible action. We will + * close the the window, restore it and check that all data has been restored. + * This includes window-specific data as well as form data for tabs. + */ +function test() { + waitForExplicitFinish(); + + let uniqueKey = "bug 394759"; + let uniqueValue = "unik" + Date.now(); + let uniqueText = "pi != " + Math.random(); + + // Clear the list of closed windows. + forgetClosedWindows(); + + provideWindow(function onTestURLLoaded(newWin) { + newWin.gBrowser.addTab().linkedBrowser.stop(); + + // Mark the window with some unique data to be restored later on. + ss.setWindowValue(newWin, uniqueKey, uniqueValue); + let [txt, chk] = newWin.content.document.querySelectorAll("#txt, #chk"); + txt.value = uniqueText; + + let browser = newWin.gBrowser.selectedBrowser; + setInputChecked(browser, {id: "chk", checked: true}).then(() => { + BrowserTestUtils.closeWindow(newWin).then(() => { + is(ss.getClosedWindowCount(), 1, + "The closed window was added to Recently Closed Windows"); + + let data = SessionStore.getClosedWindowData(false); + + // Verify that non JSON serialized data is the same as JSON serialized data. + is(JSON.stringify(data), ss.getClosedWindowData(), + "Non-serialized data is the same as serialized data") + + ok(data[0].title == TEST_URL && JSON.stringify(data[0]).indexOf(uniqueText) > -1, + "The closed window data was stored correctly"); + + // Reopen the closed window and ensure its integrity. + let newWin2 = ss.undoCloseWindow(0); + + ok(newWin2 instanceof ChromeWindow, + "undoCloseWindow actually returned a window"); + is(ss.getClosedWindowCount(), 0, + "The reopened window was removed from Recently Closed Windows"); + + // SSTabRestored will fire more than once, so we need to make sure we count them. + let restoredTabs = 0; + let expectedTabs = data[0].tabs.length; + newWin2.addEventListener("SSTabRestored", function sstabrestoredListener(aEvent) { + ++restoredTabs; + info("Restored tab " + restoredTabs + "/" + expectedTabs); + if (restoredTabs < expectedTabs) { + return; + } + + is(restoredTabs, expectedTabs, "Correct number of tabs restored"); + newWin2.removeEventListener("SSTabRestored", sstabrestoredListener, true); + + is(newWin2.gBrowser.tabs.length, 2, + "The window correctly restored 2 tabs"); + is(newWin2.gBrowser.currentURI.spec, TEST_URL, + "The window correctly restored the URL"); + + let [txt, chk] = newWin2.content.document.querySelectorAll("#txt, #chk"); + ok(txt.value == uniqueText && chk.checked, + "The window correctly restored the form"); + is(ss.getWindowValue(newWin2, uniqueKey), uniqueValue, + "The window correctly restored the data associated with it"); + + // Clean up. + BrowserTestUtils.closeWindow(newWin2).then(finish); + }, true); + }); + }); + }, TEST_URL); +} + +function setInputChecked(browser, data) { + return sendMessage(browser, "ss-test:setInputChecked", data); +} diff --git a/browser/components/sessionstore/test/browser_394759_behavior.js b/browser/components/sessionstore/test/browser_394759_behavior.js new file mode 100644 index 000000000..aa74dc061 --- /dev/null +++ b/browser/components/sessionstore/test/browser_394759_behavior.js @@ -0,0 +1,76 @@ +/** + * Test helper function that opens a series of windows, closes them + * and then checks the closed window data from SessionStore against + * expected results. + * + * @param windowsToOpen (Array) + * An array of Objects, where each object must define a single + * property "isPopup" for whether or not the opened window should + * be a popup. + * @param expectedResults (Array) + * An Object with two properies: mac and other, where each points + * at yet another Object, with the following properties: + * + * popup (int): + * The number of popup windows we expect to be in the closed window + * data. + * normal (int): + * The number of normal windows we expect to be in the closed window + * data. + * @returns Promise + */ +function testWindows(windowsToOpen, expectedResults) { + return Task.spawn(function*() { + for (let winData of windowsToOpen) { + let features = "chrome,dialog=no," + + (winData.isPopup ? "all=no" : "all"); + let url = "http://example.com/?window=" + windowsToOpen.length; + + let openWindowPromise = BrowserTestUtils.waitForNewWindow(true, url); + openDialog(getBrowserURL(), "", features, url); + let win = yield openWindowPromise; + yield BrowserTestUtils.closeWindow(win); + } + + let closedWindowData = JSON.parse(ss.getClosedWindowData()); + let numPopups = closedWindowData.filter(function(el, i, arr) { + return el.isPopup; + }).length; + let numNormal = ss.getClosedWindowCount() - numPopups; + // #ifdef doesn't work in browser-chrome tests, so do a simple regex on platform + let oResults = navigator.platform.match(/Mac/) ? expectedResults.mac + : expectedResults.other; + is(numPopups, oResults.popup, + "There were " + oResults.popup + " popup windows to reopen"); + is(numNormal, oResults.normal, + "There were " + oResults.normal + " normal windows to repoen"); + }); +} + +add_task(function* test_closed_window_states() { + // This test takes quite some time, and timeouts frequently, so we require + // more time to run. + // See Bug 518970. + requestLongerTimeout(2); + + let windowsToOpen = [{isPopup: false}, + {isPopup: false}, + {isPopup: true}, + {isPopup: true}, + {isPopup: true}]; + let expectedResults = {mac: {popup: 3, normal: 0}, + other: {popup: 3, normal: 1}}; + + yield testWindows(windowsToOpen, expectedResults); + + + let windowsToOpen2 = [{isPopup: false}, + {isPopup: false}, + {isPopup: false}, + {isPopup: false}, + {isPopup: false}]; + let expectedResults2 = {mac: {popup: 0, normal: 3}, + other: {popup: 0, normal: 3}}; + + yield testWindows(windowsToOpen2, expectedResults2); +}); \ No newline at end of file diff --git a/browser/components/sessionstore/test/browser_394759_perwindowpb.js b/browser/components/sessionstore/test/browser_394759_perwindowpb.js new file mode 100644 index 000000000..83eec3070 --- /dev/null +++ b/browser/components/sessionstore/test/browser_394759_perwindowpb.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TESTS = [ + { url: "about:config", + key: "bug 394759 Non-PB", + value: "uniq" + r() }, + { url: "about:mozilla", + key: "bug 394759 PB", + value: "uniq" + r() }, +]; + +function promiseTestOpenCloseWindow(aIsPrivate, aTest) { + return Task.spawn(function*() { + let win = yield BrowserTestUtils.openNewBrowserWindow({ "private": aIsPrivate }); + win.gBrowser.selectedBrowser.loadURI(aTest.url); + yield promiseBrowserLoaded(win.gBrowser.selectedBrowser); + yield Promise.resolve(); + // Mark the window with some unique data to be restored later on. + ss.setWindowValue(win, aTest.key, aTest.value); + yield TabStateFlusher.flushWindow(win); + // Close. + yield BrowserTestUtils.closeWindow(win); + }); +} + +function promiseTestOnWindow(aIsPrivate, aValue) { + return Task.spawn(function*() { + let win = yield BrowserTestUtils.openNewBrowserWindow({ "private": aIsPrivate }); + yield TabStateFlusher.flushWindow(win); + let data = JSON.parse(ss.getClosedWindowData())[0]; + is(ss.getClosedWindowCount(), 1, "Check that the closed window count hasn't changed"); + ok(JSON.stringify(data).indexOf(aValue) > -1, + "Check the closed window data was stored correctly"); + registerCleanupFunction(() => BrowserTestUtils.closeWindow(win)); + }); +} + +add_task(function* init() { + forgetClosedWindows(); + while (ss.getClosedTabCount(window) > 0) { + ss.forgetClosedTab(window, 0); + } +}); + +add_task(function* main() { + yield promiseTestOpenCloseWindow(false, TESTS[0]); + yield promiseTestOpenCloseWindow(true, TESTS[1]); + yield promiseTestOnWindow(false, TESTS[0].value); + yield promiseTestOnWindow(true, TESTS[0].value); +}); + diff --git a/browser/components/sessionstore/test/browser_394759_purge.js b/browser/components/sessionstore/test/browser_394759_purge.js new file mode 100644 index 000000000..75144aba1 --- /dev/null +++ b/browser/components/sessionstore/test/browser_394759_purge.js @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm"); + +function waitForClearHistory(aCallback) { + let observer = { + observe: function(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "browser:purge-domain-data"); + setTimeout(aCallback, 0); + } + }; + Services.obs.addObserver(observer, "browser:purge-domain-data", false); +} + +function test() { + waitForExplicitFinish(); + // utility functions + function countClosedTabsByTitle(aClosedTabList, aTitle) { + return aClosedTabList.filter(aData => aData.title == aTitle).length; + } + + function countOpenTabsByTitle(aOpenTabList, aTitle) { + return aOpenTabList.filter(aData => aData.entries.some(aEntry => aEntry.title == aTitle)).length; + } + + // backup old state + let oldState = ss.getBrowserState(); + let oldState_wins = JSON.parse(oldState).windows.length; + if (oldState_wins != 1) + ok(false, "oldState in test_purge has " + oldState_wins + " windows instead of 1"); + + // create a new state for testing + const REMEMBER = Date.now(), FORGET = Math.random(); + let testState = { + windows: [ { tabs: [{ entries: [{ url: "http://example.com/" }] }], selected: 1 } ], + _closedWindows : [ + // _closedWindows[0] + { + tabs: [ + { entries: [{ url: "http://example.com/", title: REMEMBER }] }, + { entries: [{ url: "http://mozilla.org/", title: FORGET }] } + ], + selected: 2, + title: "mozilla.org", + _closedTabs: [] + }, + // _closedWindows[1] + { + tabs: [ + { entries: [{ url: "http://mozilla.org/", title: FORGET }] }, + { entries: [{ url: "http://example.com/", title: REMEMBER }] }, + { entries: [{ url: "http://example.com/", title: REMEMBER }] }, + { entries: [{ url: "http://mozilla.org/", title: FORGET }] }, + { entries: [{ url: "http://example.com/", title: REMEMBER }] } + ], + selected: 5, + _closedTabs: [] + }, + // _closedWindows[2] + { + tabs: [ + { entries: [{ url: "http://example.com/", title: REMEMBER }] } + ], + selected: 1, + _closedTabs: [ + { + state: { + entries: [ + { url: "http://mozilla.org/", title: FORGET }, + { url: "http://mozilla.org/again", title: "doesn't matter" } + ] + }, + pos: 1, + title: FORGET + }, + { + state: { + entries: [ + { url: "http://example.com", title: REMEMBER } + ] + }, + title: REMEMBER + } + ] + } + ] + }; + + // set browser to test state + ss.setBrowserState(JSON.stringify(testState)); + + // purge domain & check that we purged correctly for closed windows + ForgetAboutSite.removeDataFromDomain("mozilla.org"); + waitForClearHistory(function() { + let closedWindowData = JSON.parse(ss.getClosedWindowData()); + + // First set of tests for _closedWindows[0] - tests basics + let win = closedWindowData[0]; + is(win.tabs.length, 1, "1 tab was removed"); + is(countOpenTabsByTitle(win.tabs, FORGET), 0, + "The correct tab was removed"); + is(countOpenTabsByTitle(win.tabs, REMEMBER), 1, + "The correct tab was remembered"); + is(win.selected, 1, "Selected tab has changed"); + is(win.title, REMEMBER, "The window title was correctly updated"); + + // Test more complicated case + win = closedWindowData[1]; + is(win.tabs.length, 3, "2 tabs were removed"); + is(countOpenTabsByTitle(win.tabs, FORGET), 0, + "The correct tabs were removed"); + is(countOpenTabsByTitle(win.tabs, REMEMBER), 3, + "The correct tabs were remembered"); + is(win.selected, 3, "Selected tab has changed"); + is(win.title, REMEMBER, "The window title was correctly updated"); + + // Tests handling of _closedTabs + win = closedWindowData[2]; + is(countClosedTabsByTitle(win._closedTabs, REMEMBER), 1, + "The correct number of tabs were removed, and the correct ones"); + is(countClosedTabsByTitle(win._closedTabs, FORGET), 0, + "All tabs to be forgotten were indeed removed"); + + // restore pre-test state + ss.setBrowserState(oldState); + finish(); + }); +} diff --git a/browser/components/sessionstore/test/browser_423132.js b/browser/components/sessionstore/test/browser_423132.js new file mode 100644 index 000000000..584002cff --- /dev/null +++ b/browser/components/sessionstore/test/browser_423132.js @@ -0,0 +1,59 @@ +"use strict"; + +/** + * Tests that cookies are stored and restored correctly + * by sessionstore (bug 423132). + */ +add_task(function*() { + const testURL = "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_423132_sample.html"; + + Services.cookies.removeAll(); + // make sure that sessionstore.js can be forced to be created by setting + // the interval pref to 0 + yield SpecialPowers.pushPrefEnv({ + set: [["browser.sessionstore.interval", 0]] + }); + + let win = yield BrowserTestUtils.openNewBrowserWindow(); + let browser = win.gBrowser.selectedBrowser; + browser.loadURI(testURL); + yield BrowserTestUtils.browserLoaded(browser); + + yield TabStateFlusher.flush(browser); + + // get the sessionstore state for the window + let state = ss.getWindowState(win); + + // verify our cookie got set during pageload + let enumerator = Services.cookies.enumerator; + let cookie; + let i = 0; + while (enumerator.hasMoreElements()) { + cookie = enumerator.getNext().QueryInterface(Ci.nsICookie); + i++; + } + Assert.equal(i, 1, "expected one cookie"); + + // remove the cookie + Services.cookies.removeAll(); + + // restore the window state + ss.setWindowState(win, state, true); + + // at this point, the cookie should be restored... + enumerator = Services.cookies.enumerator; + let cookie2; + while (enumerator.hasMoreElements()) { + cookie2 = enumerator.getNext().QueryInterface(Ci.nsICookie); + if (cookie.name == cookie2.name) + break; + } + is(cookie.name, cookie2.name, "cookie name successfully restored"); + is(cookie.value, cookie2.value, "cookie value successfully restored"); + is(cookie.path, cookie2.path, "cookie path successfully restored"); + + // clean up + Services.cookies.removeAll(); + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/components/sessionstore/test/browser_423132_sample.html b/browser/components/sessionstore/test/browser_423132_sample.html new file mode 100644 index 000000000..6ff7e7aa3 --- /dev/null +++ b/browser/components/sessionstore/test/browser_423132_sample.html @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/browser/components/sessionstore/test/browser_447951.js b/browser/components/sessionstore/test/browser_447951.js new file mode 100644 index 000000000..a7b6a5ee8 --- /dev/null +++ b/browser/components/sessionstore/test/browser_447951.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() { + /** Test for Bug 447951 **/ + + waitForExplicitFinish(); + const baseURL = "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_447951_sample.html#"; + + // Make sure the functionality added in bug 943339 doesn't affect the results + gPrefService.setIntPref("browser.sessionstore.max_serialize_back", -1); + gPrefService.setIntPref("browser.sessionstore.max_serialize_forward", -1); + registerCleanupFunction(function () { + gPrefService.clearUserPref("browser.sessionstore.max_serialize_back"); + gPrefService.clearUserPref("browser.sessionstore.max_serialize_forward"); + }); + + let tab = gBrowser.addTab(); + promiseBrowserLoaded(tab.linkedBrowser).then(() => { + let tabState = { entries: [] }; + let max_entries = gPrefService.getIntPref("browser.sessionhistory.max_entries"); + for (let i = 0; i < max_entries; i++) + tabState.entries.push({ url: baseURL + i }); + + promiseTabState(tab, tabState).then(() => { + return TabStateFlusher.flush(tab.linkedBrowser); + }).then(() => { + tabState = JSON.parse(ss.getTabState(tab)); + is(tabState.entries.length, max_entries, "session history filled to the limit"); + is(tabState.entries[0].url, baseURL + 0, "... but not more"); + + // visit yet another anchor (appending it to session history) + ContentTask.spawn(tab.linkedBrowser, null, function() { + content.window.document.querySelector("a").click(); + }).then(flushAndCheck); + + function flushAndCheck() { + TabStateFlusher.flush(tab.linkedBrowser).then(check); + } + + function check() { + tabState = JSON.parse(ss.getTabState(tab)); + if (tab.linkedBrowser.currentURI.spec != baseURL + "end") { + // It may take a few passes through the event loop before we + // get the right URL. + executeSoon(flushAndCheck); + return; + } + + is(tab.linkedBrowser.currentURI.spec, baseURL + "end", + "the new anchor was loaded"); + is(tabState.entries[tabState.entries.length - 1].url, baseURL + "end", + "... and ignored"); + is(tabState.entries[0].url, baseURL + 1, + "... and the first item was removed"); + + // clean up + gBrowser.removeTab(tab); + finish(); + } + }); + }); +} diff --git a/browser/components/sessionstore/test/browser_447951_sample.html b/browser/components/sessionstore/test/browser_447951_sample.html new file mode 100644 index 000000000..00282f25e --- /dev/null +++ b/browser/components/sessionstore/test/browser_447951_sample.html @@ -0,0 +1,5 @@ + + +Testcase for bug 447951 + +click me diff --git a/browser/components/sessionstore/test/browser_454908.js b/browser/components/sessionstore/test/browser_454908.js new file mode 100644 index 000000000..fb8206e2f --- /dev/null +++ b/browser/components/sessionstore/test/browser_454908.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const URL = ROOT + "browser_454908_sample.html"; +const PASS = "pwd-" + Math.random(); + +/** + * Bug 454908 - Don't save/restore values of password fields. + */ +add_task(function* test_dont_save_passwords() { + // Make sure we do save form data. + Services.prefs.clearUserPref("browser.sessionstore.privacy_level"); + + // Add a tab with a password field. + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Fill in some values. + let usernameValue = "User " + Math.random(); + yield setInputValue(browser, {id: "username", value: usernameValue}); + yield setInputValue(browser, {id: "passwd", value: PASS}); + + // Close and restore the tab. + yield promiseRemoveTab(tab); + tab = ss.undoCloseTab(window, 0); + browser = tab.linkedBrowser; + yield promiseTabRestored(tab); + + // Check that password fields aren't saved/restored. + let username = yield getInputValue(browser, {id: "username"}); + is(username, usernameValue, "username was saved/restored"); + let passwd = yield getInputValue(browser, {id: "passwd"}); + is(passwd, "", "password wasn't saved/restored"); + + // Write to disk and read our file. + yield forceSaveState(); + yield promiseForEachSessionRestoreFile((state, key) => + // Ensure that we have not saved our password. + ok(!state.includes(PASS), "password has not been written to file " + key) + ); + + // Cleanup. + gBrowser.removeTab(tab); +}); diff --git a/browser/components/sessionstore/test/browser_454908_sample.html b/browser/components/sessionstore/test/browser_454908_sample.html new file mode 100644 index 000000000..02f40bf20 --- /dev/null +++ b/browser/components/sessionstore/test/browser_454908_sample.html @@ -0,0 +1,8 @@ + +Test for bug 454908 + +

Dummy Login

+
+

Username: +

Password: +

diff --git a/browser/components/sessionstore/test/browser_456342.js b/browser/components/sessionstore/test/browser_456342.js new file mode 100644 index 000000000..d7ed33ee5 --- /dev/null +++ b/browser/components/sessionstore/test/browser_456342.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const URL = ROOT + "browser_456342_sample.xhtml"; + +/** + * Bug 456342 - Restore values from non-standard input field types. + */ +add_task(function test_restore_nonstandard_input_values() { + // Add tab with various non-standard input field types. + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Fill in form values. + let expectedValue = Math.random(); + yield setFormElementValues(browser, {value: expectedValue}); + + // Remove tab and check collected form data. + yield promiseRemoveTab(tab); + let undoItems = JSON.parse(ss.getClosedTabData(window)); + let savedFormData = undoItems[0].state.formdata; + + let countGood = 0, countBad = 0; + for (let id of Object.keys(savedFormData.id)) { + if (savedFormData.id[id] == expectedValue) { + countGood++; + } else { + countBad++; + } + } + + for (let exp of Object.keys(savedFormData.xpath)) { + if (savedFormData.xpath[exp] == expectedValue) { + countGood++; + } else { + countBad++; + } + } + + is(countGood, 4, "Saved text for non-standard input fields"); + is(countBad, 0, "Didn't save text for ignored field types"); +}); + +function setFormElementValues(browser, data) { + return sendMessage(browser, "ss-test:setFormElementValues", data); +} diff --git a/browser/components/sessionstore/test/browser_456342_sample.xhtml b/browser/components/sessionstore/test/browser_456342_sample.xhtml new file mode 100644 index 000000000..a08777a8d --- /dev/null +++ b/browser/components/sessionstore/test/browser_456342_sample.xhtml @@ -0,0 +1,36 @@ + + + +Test for bug 456342 + + +
+

Non-standard <input>s

+

Search

+

Image Search:

+

Autocomplete:

+

Mistyped:

+ +

Ignored types

+ + + + + + + + + + + + + + +

Textarea with unchanged text

+ + + +

file field with changed value

+ + +

file field with unchanged value

+ + + + +

Select menu with changed selection

+ + +

Select menu with unchanged selection (change event still fires)

+ + +

Multiple Select menu with changed selection

+ + +

Select menu with unchanged selection

+ + +

checkbox with changed value

+ + + +

checkbox with unchanged value

+ + + +

radio with changed value

+Radio 1 +Radio 2 +Radio 3 + +

radio with unchanged value

+Radio 4 +Radio 5 +Radio 6 + +

Changed field IDs

+
+
+
diff --git a/browser/components/sessionstore/test/browser_formdata.js b/browser/components/sessionstore/test/browser_formdata.js new file mode 100644 index 000000000..ce1272888 --- /dev/null +++ b/browser/components/sessionstore/test/browser_formdata.js @@ -0,0 +1,194 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(2); + +/** + * This test ensures that form data collection respects the privacy level as + * set by the user. + */ +add_task(function test_formdata() { + const URL = "http://mochi.test:8888/browser/browser/components/" + + "sessionstore/test/browser_formdata_sample.html"; + + const OUTER_VALUE = "browser_formdata_" + Math.random(); + const INNER_VALUE = "browser_formdata_" + Math.random(); + + // Creates a tab, loads a page with some form fields, + // modifies their values and closes the tab. + function createAndRemoveTab() { + return Task.spawn(function () { + // Create a new tab. + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Modify form data. + yield setInputValue(browser, {id: "txt", value: OUTER_VALUE}); + yield setInputValue(browser, {id: "txt", value: INNER_VALUE, frame: 0}); + + // Remove the tab. + yield promiseRemoveTab(tab); + }); + } + + yield createAndRemoveTab(); + let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window)); + is(formdata.id.txt, OUTER_VALUE, "outer value is correct"); + is(formdata.children[0].id.txt, INNER_VALUE, "inner value is correct"); + + // Disable saving data for encrypted sites. + Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1); + + yield createAndRemoveTab(); + [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window)); + is(formdata.id.txt, OUTER_VALUE, "outer value is correct"); + ok(!formdata.children, "inner value was *not* stored"); + + // Disable saving data for any site. + Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2); + + yield createAndRemoveTab(); + [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window)); + ok(!formdata, "form data has *not* been stored"); + + // Restore the default privacy level. + Services.prefs.clearUserPref("browser.sessionstore.privacy_level"); +}); + +/** + * This test ensures that a malicious website can't trick us into restoring + * form data into a wrong website and that we always check the stored URL + * before doing so. + */ +add_task(function test_url_check() { + const URL = "data:text/html;charset=utf-8,"; + const VALUE = "value-" + Math.random(); + + // Create a tab with an iframe containing an input field. + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Restore a tab state with a given form data url. + function restoreStateWithURL(url) { + let state = {entries: [{url: URL}], formdata: {id: {input: VALUE}}}; + + if (url) { + state.formdata.url = url; + } + + return promiseTabState(tab, state).then(() => getInputValue(browser, "input")); + } + + // Check that the form value is restored with the correct URL. + is((yield restoreStateWithURL(URL)), VALUE, "form data restored"); + + // Check that the form value is *not* restored with the wrong URL. + is((yield restoreStateWithURL(URL + "?")), "", "form data not restored"); + is((yield restoreStateWithURL()), "", "form data not restored"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +/** + * This test ensures that collecting form data works as expected when having + * nested frame sets. + */ +add_task(function test_nested() { + const URL = "data:text/html;charset=utf-8," + + "" + + "clickme" + + "clickme"; + + // Create a new tab. + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Check that we have a single shistory entry. + yield TabStateFlusher.flush(browser); + let {entries} = JSON.parse(ss.getTabState(tab)); + is(entries.length, 1, "there is one shistory entry"); + is(entries[0].children.length, 1, "the entry has one child"); + + // Navigate the subframe. + browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a1"}); + yield promiseBrowserLoaded(browser, false /* don't ignore subframes */); + + // Check shistory. + yield TabStateFlusher.flush(browser); + ({entries} = JSON.parse(ss.getTabState(tab))); + is(entries.length, 2, "there now are two shistory entries"); + is(entries[1].children.length, 1, "the second entry has one child"); + + // Go back in history. + browser.goBack(); + yield promiseBrowserLoaded(browser, false /* don't ignore subframes */); + + // Navigate the subframe again. + browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a2"}); + yield promiseContentMessage(browser, "ss-test:hashchange"); + + // Check shistory. + yield TabStateFlusher.flush(browser); + ({entries} = JSON.parse(ss.getTabState(tab))); + is(entries.length, 2, "there now are two shistory entries"); + is(entries[1].children.length, 1, "the second entry has one child"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +/** + * Ensure that navigating from an about page invalidates shistory. + */ +add_task(function test_about_page_navigate() { + // Create a new tab. + let tab = gBrowser.addTab("about:blank"); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Check that we have a single shistory entry. + yield TabStateFlusher.flush(browser); + let {entries} = JSON.parse(ss.getTabState(tab)); + is(entries.length, 1, "there is one shistory entry"); + is(entries[0].url, "about:blank", "url is correct"); + + browser.loadURI("about:robots"); + yield promiseBrowserLoaded(browser); + + // Check that we have changed the history entry. + yield TabStateFlusher.flush(browser); + ({entries} = JSON.parse(ss.getTabState(tab))); + is(entries.length, 1, "there is one shistory entry"); + is(entries[0].url, "about:robots", "url is correct"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +/** + * Ensure that history.pushState and history.replaceState invalidate shistory. + */ +add_task(function test_pushstate_replacestate() { + // Create a new tab. + let tab = gBrowser.addTab("http://example.com/1"); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Check that we have a single shistory entry. + yield TabStateFlusher.flush(browser); + let {entries} = JSON.parse(ss.getTabState(tab)); + is(entries.length, 1, "there is one shistory entry"); + is(entries[0].url, "http://example.com/1", "url is correct"); + + yield ContentTask.spawn(browser, {}, function* () { + content.window.history.pushState({}, "", 'test-entry/'); + }); + + // Check that we have added the history entry. + yield TabStateFlusher.flush(browser); + ({entries} = JSON.parse(ss.getTabState(tab))); + is(entries.length, 2, "there is another shistory entry"); + is(entries[1].url, "http://example.com/test-entry/", "url is correct"); + + yield ContentTask.spawn(browser, {}, function* () { + content.window.history.replaceState({}, "", "test-entry2/"); + }); + + // Check that we have modified the history entry. + yield TabStateFlusher.flush(browser); + ({entries} = JSON.parse(ss.getTabState(tab))); + is(entries.length, 2, "there is still two shistory entries"); + is(entries[1].url, "http://example.com/test-entry/test-entry2/", "url is correct"); + + // Cleanup. + gBrowser.removeTab(tab); +}); + +/** + * Ensure that slow loading subframes will invalidate shistory. + */ +add_task(function test_slow_subframe_load() { + const SLOW_URL = "http://mochi.test:8888/browser/browser/components/" + + "sessionstore/test/browser_sessionHistory_slow.sjs"; + + const URL = "data:text/html;charset=utf-8," + + "" + + "" + + ""; + + // Add a new tab with a slow loading subframe + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + yield TabStateFlusher.flush(browser); + let {entries} = JSON.parse(ss.getTabState(tab)); + + // Check the number of children. + is(entries.length, 1, "there is one root entry ..."); + is(entries[0].children.length, 1, "... with one child entries"); + + // Check URLs. + ok(entries[0].url.startsWith("data:text/html"), "correct root url"); + is(entries[0].children[0].url, SLOW_URL, "correct url for subframe"); + + // Cleanup. + gBrowser.removeTab(tab); +}); diff --git a/browser/components/sessionstore/test/browser_sessionHistory_slow.sjs b/browser/components/sessionstore/test/browser_sessionHistory_slow.sjs new file mode 100644 index 000000000..41da3c2ad --- /dev/null +++ b/browser/components/sessionstore/test/browser_sessionHistory_slow.sjs @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const Cc = Components.classes; +const Ci = Components.interfaces; + +const DELAY_MS = "2000"; + +let timer; + +function handleRequest(req, resp) { + resp.processAsync(); + resp.setHeader("Cache-Control", "no-cache", false); + resp.setHeader("Content-Type", "text/html;charset=utf-8", false); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(() => { + resp.write("hi"); + resp.finish(); + }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT); +} diff --git a/browser/components/sessionstore/test/browser_sessionStorage.html b/browser/components/sessionstore/test/browser_sessionStorage.html new file mode 100644 index 000000000..7e2dccf4a --- /dev/null +++ b/browser/components/sessionstore/test/browser_sessionStorage.html @@ -0,0 +1,27 @@ + + + + + browser_sessionStorage.html + + + + + diff --git a/browser/components/sessionstore/test/browser_sessionStorage.js b/browser/components/sessionstore/test/browser_sessionStorage.js new file mode 100644 index 000000000..b580c5cc2 --- /dev/null +++ b/browser/components/sessionstore/test/browser_sessionStorage.js @@ -0,0 +1,188 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const RAND = Math.random(); +const URL = "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_sessionStorage.html" + + "?" + RAND; + +const OUTER_VALUE = "outer-value-" + RAND; +const INNER_VALUE = "inner-value-" + RAND; + +/** + * This test ensures that setting, modifying and restoring sessionStorage data + * works as expected. + */ +add_task(function session_storage() { + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Flush to make sure chrome received all data. + yield TabStateFlusher.flush(browser); + + let {storage} = JSON.parse(ss.getTabState(tab)); + is(storage["http://example.com"].test, INNER_VALUE, + "sessionStorage data for example.com has been serialized correctly"); + is(storage["http://mochi.test:8888"].test, OUTER_VALUE, + "sessionStorage data for mochi.test has been serialized correctly"); + + // Ensure that modifying sessionStore values works for the inner frame only. + yield modifySessionStorage(browser, {test: "modified1"}, {frameIndex: 0}); + yield TabStateFlusher.flush(browser); + + ({storage} = JSON.parse(ss.getTabState(tab))); + is(storage["http://example.com"].test, "modified1", + "sessionStorage data for example.com has been serialized correctly"); + is(storage["http://mochi.test:8888"].test, OUTER_VALUE, + "sessionStorage data for mochi.test has been serialized correctly"); + + // Ensure that modifying sessionStore values works for both frames. + yield modifySessionStorage(browser, {test: "modified"}); + yield modifySessionStorage(browser, {test: "modified2"}, {frameIndex: 0}); + yield TabStateFlusher.flush(browser); + + ({storage} = JSON.parse(ss.getTabState(tab))); + is(storage["http://example.com"].test, "modified2", + "sessionStorage data for example.com has been serialized correctly"); + is(storage["http://mochi.test:8888"].test, "modified", + "sessionStorage data for mochi.test has been serialized correctly"); + + // Test that duplicating a tab works. + let tab2 = gBrowser.duplicateTab(tab); + let browser2 = tab2.linkedBrowser; + yield promiseTabRestored(tab2); + + // Flush to make sure chrome received all data. + yield TabStateFlusher.flush(browser2); + + ({storage} = JSON.parse(ss.getTabState(tab2))); + is(storage["http://example.com"].test, "modified2", + "sessionStorage data for example.com has been duplicated correctly"); + is(storage["http://mochi.test:8888"].test, "modified", + "sessionStorage data for mochi.test has been duplicated correctly"); + + // Ensure that the content script retains restored data + // (by e.g. duplicateTab) and sends it along with new data. + yield modifySessionStorage(browser2, {test: "modified3"}); + yield TabStateFlusher.flush(browser2); + + ({storage} = JSON.parse(ss.getTabState(tab2))); + is(storage["http://example.com"].test, "modified2", + "sessionStorage data for example.com has been duplicated correctly"); + is(storage["http://mochi.test:8888"].test, "modified3", + "sessionStorage data for mochi.test has been duplicated correctly"); + + // Check that loading a new URL discards data. + browser2.loadURI("http://mochi.test:8888/"); + yield promiseBrowserLoaded(browser2); + yield TabStateFlusher.flush(browser2); + + ({storage} = JSON.parse(ss.getTabState(tab2))); + is(storage["http://mochi.test:8888"].test, "modified3", + "navigating retains correct storage data"); + ok(!storage["http://example.com"], "storage data was discarded"); + + // Check that loading a new URL discards data. + browser2.loadURI("about:mozilla"); + yield promiseBrowserLoaded(browser2); + yield TabStateFlusher.flush(browser2); + + let state = JSON.parse(ss.getTabState(tab2)); + ok(!state.hasOwnProperty("storage"), "storage data was discarded"); + + // Clean up. + yield promiseRemoveTab(tab); + yield promiseRemoveTab(tab2); +}); + +/** + * This test ensures that purging domain data also purges data from the + * sessionStorage data collected for tabs. + */ +add_task(function purge_domain() { + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Purge data for "mochi.test". + yield purgeDomainData(browser, "mochi.test"); + + // Flush to make sure chrome received all data. + yield TabStateFlusher.flush(browser); + + let {storage} = JSON.parse(ss.getTabState(tab)); + ok(!storage["http://mochi.test:8888"], + "sessionStorage data for mochi.test has been purged"); + is(storage["http://example.com"].test, INNER_VALUE, + "sessionStorage data for example.com has been preserved"); + + yield promiseRemoveTab(tab); +}); + +/** + * This test ensures that collecting sessionStorage data respects the privacy + * levels as set by the user. + */ +add_task(function respect_privacy_level() { + let tab = gBrowser.addTab(URL + "&secure"); + yield promiseBrowserLoaded(tab.linkedBrowser); + yield promiseRemoveTab(tab); + + let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); + is(storage["http://mochi.test:8888"].test, OUTER_VALUE, + "http sessionStorage data has been saved"); + is(storage["https://example.com"].test, INNER_VALUE, + "https sessionStorage data has been saved"); + + // Disable saving data for encrypted sites. + Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1); + + tab = gBrowser.addTab(URL + "&secure"); + yield promiseBrowserLoaded(tab.linkedBrowser); + yield promiseRemoveTab(tab); + + [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); + is(storage["http://mochi.test:8888"].test, OUTER_VALUE, + "http sessionStorage data has been saved"); + ok(!storage["https://example.com"], + "https sessionStorage data has *not* been saved"); + + // Disable saving data for any site. + Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2); + + // Check that duplicating a tab copies all private data. + tab = gBrowser.addTab(URL + "&secure"); + yield promiseBrowserLoaded(tab.linkedBrowser); + let tab2 = gBrowser.duplicateTab(tab); + yield promiseTabRestored(tab2); + yield promiseRemoveTab(tab); + + // With privacy_level=2 the |tab| shouldn't have any sessionStorage data. + [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); + ok(!storage, "sessionStorage data has *not* been saved"); + + // Remove all closed tabs before continuing with the next test. + // As Date.now() isn't monotonic we might sometimes check + // the wrong closedTabData entry. + while (ss.getClosedTabCount(window) > 0) { + ss.forgetClosedTab(window, 0); + } + + // Restore the default privacy level and close the duplicated tab. + Services.prefs.clearUserPref("browser.sessionstore.privacy_level"); + yield promiseRemoveTab(tab2); + + // With privacy_level=0 the duplicated |tab2| should persist all data. + [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); + is(storage["http://mochi.test:8888"].test, OUTER_VALUE, + "http sessionStorage data has been saved"); + is(storage["https://example.com"].test, INNER_VALUE, + "https sessionStorage data has been saved"); +}); + +function purgeDomainData(browser, domain) { + return sendMessage(browser, "ss-test:purgeDomainData", domain); +} diff --git a/browser/components/sessionstore/test/browser_sessionStorage_size.js b/browser/components/sessionstore/test/browser_sessionStorage_size.js new file mode 100644 index 000000000..d1d894611 --- /dev/null +++ b/browser/components/sessionstore/test/browser_sessionStorage_size.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const RAND = Math.random(); +const URL = "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_sessionStorage.html" + + "?" + RAND; + +const OUTER_VALUE = "outer-value-" + RAND; + +// Test that we record the size of messages. +add_task(function* test_telemetry() { + Services.telemetry.canRecordExtended = true; + let histogram = Services.telemetry.getHistogramById("FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS"); + let snap1 = histogram.snapshot(); + + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Flush to make sure chrome received all data. + yield TabStateFlusher.flush(browser); + let snap2 = histogram.snapshot(); + + Assert.ok(snap2.counts[5] > snap1.counts[5]); + yield promiseRemoveTab(tab); + Services.telemetry.canRecordExtended = false; +}); + +// Lower the size limit for DOM Storage content. Check that DOM Storage +// is not updated, but that other things remain updated. +add_task(function* test_large_content() { + Services.prefs.setIntPref("browser.sessionstore.dom_storage_limit", 5); + + let tab = gBrowser.addTab(URL); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + + // Flush to make sure chrome received all data. + yield TabStateFlusher.flush(browser); + + let state = JSON.parse(ss.getTabState(tab)); + info(JSON.stringify(state, null, "\t")); + Assert.equal(state.storage, null, "We have no storage for the tab"); + Assert.equal(state.entries[0].title, OUTER_VALUE); + yield promiseRemoveTab(tab); + + Services.prefs.clearUserPref("browser.sessionstore.dom_storage_limit"); +}); diff --git a/browser/components/sessionstore/test/browser_sessionStoreContainer.js b/browser/components/sessionstore/test/browser_sessionStoreContainer.js new file mode 100644 index 000000000..1bc9537e2 --- /dev/null +++ b/browser/components/sessionstore/test/browser_sessionStoreContainer.js @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +add_task(function* () { + for (let i = 0; i < 3; ++i) { + let tab = gBrowser.addTab("http://example.com/", { userContextId: i }); + let browser = tab.linkedBrowser; + + yield promiseBrowserLoaded(browser); + + let tab2 = gBrowser.duplicateTab(tab); + Assert.equal(tab2.getAttribute("usercontextid"), i); + let browser2 = tab2.linkedBrowser; + yield promiseTabRestored(tab2) + + yield ContentTask.spawn(browser2, { expectedId: i }, function* (args) { + let loadContext = docShell.QueryInterface(Ci.nsILoadContext); + Assert.equal(loadContext.originAttributes.userContextId, + args.expectedId, "The docShell has the correct userContextId"); + }); + + yield promiseRemoveTab(tab); + yield promiseRemoveTab(tab2); + } +}); + +add_task(function* () { + let tab = gBrowser.addTab("http://example.com/", { userContextId: 1 }); + let browser = tab.linkedBrowser; + + yield promiseBrowserLoaded(browser); + + gBrowser.selectedTab = tab; + + let tab2 = gBrowser.duplicateTab(tab); + let browser2 = tab2.linkedBrowser; + yield promiseTabRestored(tab2) + + yield ContentTask.spawn(browser2, { expectedId: 1 }, function* (args) { + Assert.equal(docShell.getOriginAttributes().userContextId, + args.expectedId, + "The docShell has the correct userContextId"); + }); + + yield promiseRemoveTab(tab); + yield promiseRemoveTab(tab2); +}); + +add_task(function* () { + let tab = gBrowser.addTab("http://example.com/", { userContextId: 1 }); + let browser = tab.linkedBrowser; + + yield promiseBrowserLoaded(browser); + + gBrowser.removeTab(tab); + + let tab2 = ss.undoCloseTab(window, 0); + Assert.equal(tab2.getAttribute("usercontextid"), 1); + yield promiseTabRestored(tab2); + yield ContentTask.spawn(tab2.linkedBrowser, { expectedId: 1 }, function* (args) { + Assert.equal(docShell.getOriginAttributes().userContextId, + args.expectedId, + "The docShell has the correct userContextId"); + }); + + yield promiseRemoveTab(tab2); +}); + +// Opens "uri" in a new tab with the provided userContextId and focuses it. +// Returns the newly opened tab. +function* openTabInUserContext(userContextId) { + // Open the tab in the correct userContextId. + let tab = gBrowser.addTab("http://example.com", { userContextId }); + + // Select tab and make sure its browser is focused. + gBrowser.selectedTab = tab; + tab.ownerGlobal.focus(); + + let browser = gBrowser.getBrowserForTab(tab); + yield BrowserTestUtils.browserLoaded(browser); + return { tab, browser }; +} + +function waitForNewCookie() { + return new Promise(resolve => { + Services.obs.addObserver(function observer(subj, topic, data) { + let cookie = subj.QueryInterface(Ci.nsICookie2); + if (data == "added") { + Services.obs.removeObserver(observer, topic); + resolve(); + } + }, "cookie-changed", false); + }); +} + +add_task(function* test() { + const USER_CONTEXTS = [ + "default", + "personal", + "work", + ]; + + const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + const { TabStateFlusher } = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + + // Make sure userContext is enabled. + yield SpecialPowers.pushPrefEnv({ + "set": [ [ "privacy.userContext.enabled", true ] ] + }); + + let lastSessionRestore; + for (let userContextId of Object.keys(USER_CONTEXTS)) { + // Load the page in 3 different contexts and set a cookie + // which should only be visible in that context. + let cookie = USER_CONTEXTS[userContextId]; + + // Open our tab in the given user context. + let { tab, browser } = yield* openTabInUserContext(userContextId); + + yield Promise.all([ + waitForNewCookie(), + ContentTask.spawn(browser, cookie, cookie => content.document.cookie = cookie) + ]); + + // Ensure the tab's session history is up-to-date. + yield TabStateFlusher.flush(browser); + + lastSessionRestore = ss.getWindowState(window); + + // Remove the tab. + gBrowser.removeTab(tab); + } + + let state = JSON.parse(lastSessionRestore); + is(state.windows[0].cookies.length, USER_CONTEXTS.length, + "session restore should have each container's cookie"); +}); + diff --git a/browser/components/sessionstore/test/browser_swapDocShells.js b/browser/components/sessionstore/test/browser_swapDocShells.js new file mode 100644 index 000000000..839f060e7 --- /dev/null +++ b/browser/components/sessionstore/test/browser_swapDocShells.js @@ -0,0 +1,35 @@ +"use strict"; + +add_task(function* () { + let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla"); + yield promiseBrowserLoaded(gBrowser.selectedBrowser); + + let win = gBrowser.replaceTabWithWindow(tab); + yield promiseDelayedStartupFinished(win); + yield promiseBrowserHasURL(win.gBrowser.browsers[0], "about:mozilla"); + + win.duplicateTabIn(win.gBrowser.selectedTab, "tab"); + yield promiseTabRestored(win.gBrowser.tabs[1]); + + let browser = win.gBrowser.browsers[1]; + is(browser.currentURI.spec, "about:mozilla", "tab was duplicated"); + + yield BrowserTestUtils.closeWindow(win); +}); + +function promiseDelayedStartupFinished(win) { + let deferred = Promise.defer(); + whenDelayedStartupFinished(win, deferred.resolve); + return deferred.promise; +} + +function promiseBrowserHasURL(browser, url) { + let promise = Promise.resolve(); + + if (browser.contentDocument.readyState === "complete" && + browser.currentURI.spec === url) { + return promise; + } + + return promise.then(() => promiseBrowserHasURL(browser, url)); +} diff --git a/browser/components/sessionstore/test/browser_switch_remoteness.js b/browser/components/sessionstore/test/browser_switch_remoteness.js new file mode 100644 index 000000000..9eb8c260a --- /dev/null +++ b/browser/components/sessionstore/test/browser_switch_remoteness.js @@ -0,0 +1,49 @@ +"use strict"; + +const URL = "http://example.com/browser_switch_remoteness_"; + +function countHistoryEntries(browser, expected) { + return ContentTask.spawn(browser, { expected }, function* (args) { + let Ci = Components.interfaces; + let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation); + let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal); + Assert.equal(history && history.count, args.expected, + "correct number of shistory entries"); + }); +} + +add_task(function* () { + // Open a new window. + let win = yield promiseNewWindowLoaded(); + + // Add a new tab. + let tab = win.gBrowser.addTab("about:blank"); + let browser = tab.linkedBrowser; + yield promiseBrowserLoaded(browser); + ok(browser.isRemoteBrowser, "browser is remote"); + + // Get the maximum number of preceding entries to save. + const MAX_BACK = Services.prefs.getIntPref("browser.sessionstore.max_serialize_back"); + ok(MAX_BACK > -1, "check that the default has a value that caps data"); + + // Load more pages than we would save to disk on a clean shutdown. + for (let i = 0; i < MAX_BACK + 2; i++) { + browser.loadURI(URL + i); + yield promiseBrowserLoaded(browser); + ok(browser.isRemoteBrowser, "browser is still remote"); + } + + // Check we have the right number of shistory entries. + yield countHistoryEntries(browser, MAX_BACK + 2); + + // Load a non-remote page. + browser.loadURI("about:robots"); + yield promiseTabRestored(tab); + ok(!browser.isRemoteBrowser, "browser is not remote anymore"); + + // Check that we didn't lose any shistory entries. + yield countHistoryEntries(browser, MAX_BACK + 3); + + // Cleanup. + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/components/sessionstore/test/browser_undoCloseById.js b/browser/components/sessionstore/test/browser_undoCloseById.js new file mode 100644 index 000000000..f2f0f919c --- /dev/null +++ b/browser/components/sessionstore/test/browser_undoCloseById.js @@ -0,0 +1,118 @@ +"use strict"; + +/** + * This test is for the undoCloseById function. + */ + +Cu.import("resource:///modules/sessionstore/SessionStore.jsm"); + +function openAndCloseTab(window, url) { + let tab = window.gBrowser.addTab(url); + yield promiseBrowserLoaded(tab.linkedBrowser, true, url); + yield TabStateFlusher.flush(tab.linkedBrowser); + yield promiseRemoveTab(tab); +} + +function* openWindow(url) { + let win = yield promiseNewWindowLoaded(); + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; + win.gBrowser.selectedBrowser.loadURIWithFlags(url, flags); + yield promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url); + return win; +} + +function closeWindow(win) { + yield BrowserTestUtils.closeWindow(win); + // Wait 20 ms to allow SessionStorage a chance to register the closed window. + yield new Promise(resolve => setTimeout(resolve, 20)); +} + +add_task(function* test_undoCloseById() { + // Clear the lists of closed windows and tabs. + forgetClosedWindows(); + while (SessionStore.getClosedTabCount(window)) { + SessionStore.forgetClosedTab(window, 0); + } + + // Open a new window. + let win = yield openWindow("about:robots"); + + // Open and close a tab. + yield openAndCloseTab(win, "about:mozilla"); + is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab"); + + // Record the first closedId created. + let initialClosedId = SessionStore.getClosedTabData(win, false)[0].closedId; + + // Open and close another window. + let win2 = yield openWindow("about:mozilla"); + yield closeWindow(win2); // closedId == initialClosedId + 1 + is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window"); + + // Open and close another tab in the first window. + yield openAndCloseTab(win, "about:robots"); // closedId == initialClosedId + 2 + is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab"); + + // Undo closing the second tab. + let tab = SessionStore.undoCloseById(initialClosedId + 2); + yield promiseBrowserLoaded(tab.linkedBrowser); + is(tab.linkedBrowser.currentURI.spec, "about:robots", "The expected tab was re-opened"); + + let notTab = SessionStore.undoCloseById(initialClosedId + 2); + is(notTab, undefined, "Re-opened tab cannot be unClosed again by closedId"); + + // Now the last closed object should be a window again. + is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window"); + + // Undo closing the first tab. + let tab2 = SessionStore.undoCloseById(initialClosedId); + yield promiseBrowserLoaded(tab2.linkedBrowser); + is(tab2.linkedBrowser.currentURI.spec, "about:mozilla", "The expected tab was re-opened"); + + // Close the two tabs we re-opened. + yield promiseRemoveTab(tab); // closedId == initialClosedId + 3 + is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab"); + yield promiseRemoveTab(tab2); // closedId == initialClosedId + 4 + is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab"); + + // Open another new window. + let win3 = yield openWindow("about:mozilla"); + + // Close both windows. + yield closeWindow(win); // closedId == initialClosedId + 5 + is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window"); + yield closeWindow(win3); // closedId == initialClosedId + 6 + is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window"); + + // Undo closing the second window. + win = SessionStore.undoCloseById(initialClosedId + 6); + yield BrowserTestUtils.waitForEvent(win, "load"); + + // Make sure we wait until this window is restored. + yield BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, + "SSTabRestored"); + + is(win.gBrowser.selectedBrowser.currentURI.spec, "about:mozilla", "The expected window was re-opened"); + + let notWin = SessionStore.undoCloseById(initialClosedId + 6); + is(notWin, undefined, "Re-opened window cannot be unClosed again by closedId"); + + // Close the window again. + yield closeWindow(win); + is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window"); + + // Undo closing the first window. + win = SessionStore.undoCloseById(initialClosedId + 5); + + yield BrowserTestUtils.waitForEvent(win, "load"); + + // Make sure we wait until this window is restored. + yield BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, + "SSTabRestored"); + + is(win.gBrowser.selectedBrowser.currentURI.spec, "about:robots", "The expected window was re-opened"); + + // Close the window again. + yield closeWindow(win); + is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window"); +}); diff --git a/browser/components/sessionstore/test/browser_unrestored_crashedTabs.js b/browser/components/sessionstore/test/browser_unrestored_crashedTabs.js new file mode 100644 index 000000000..e46348e59 --- /dev/null +++ b/browser/components/sessionstore/test/browser_unrestored_crashedTabs.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that if we have tabs that are still in the "click to + * restore" state, that if their browsers crash, that we don't + * show the crashed state for those tabs (since selecting them + * should restore them anyway). + */ + +const PREF = "browser.sessionstore.restore_on_demand"; +const PAGE = "data:text/html,A%20regular,%20everyday,%20normal%20page."; + +add_task(function* test() { + yield pushPrefs([PREF, true]); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, function*(browser) { + yield TabStateFlusher.flush(browser); + + // We'll create a second "pending" tab. This is the one we'll + // ensure doesn't go to about:tabcrashed. We start it non-remote + // since this is how SessionStore creates all browsers before + // they are restored. + let unrestoredTab = gBrowser.addTab("about:blank", { + skipAnimation: true, + forceNotRemote: true, + }); + + let state = { + entries: [{url: PAGE}], + }; + + ss.setTabState(unrestoredTab, JSON.stringify(state)); + + ok(!unrestoredTab.hasAttribute("crashed"), "tab is not crashed"); + ok(unrestoredTab.hasAttribute("pending"), "tab is pending"); + + // Now crash the selected browser. + yield BrowserTestUtils.crashBrowser(browser); + + ok(!unrestoredTab.hasAttribute("crashed"), "tab is still not crashed"); + ok(unrestoredTab.hasAttribute("pending"), "tab is still pending"); + + // Selecting the tab should now restore it. + gBrowser.selectedTab = unrestoredTab; + yield promiseTabRestored(unrestoredTab); + + ok(!unrestoredTab.hasAttribute("crashed"), "tab is still not crashed"); + ok(!unrestoredTab.hasAttribute("pending"), "tab is no longer pending"); + + // The original tab should still be crashed + let originalTab = gBrowser.getTabForBrowser(browser); + ok(originalTab.hasAttribute("crashed"), "original tab is crashed"); + ok(!originalTab.isRemoteBrowser, "Should not be remote"); + + // We'd better be able to restore it still. + gBrowser.selectedTab = originalTab; + SessionStore.reviveCrashedTab(originalTab); + yield promiseTabRestored(originalTab); + + // Clean up. + yield BrowserTestUtils.removeTab(unrestoredTab); + }); +}); diff --git a/browser/components/sessionstore/test/browser_upgrade_backup.js b/browser/components/sessionstore/test/browser_upgrade_backup.js new file mode 100644 index 000000000..768671051 --- /dev/null +++ b/browser/components/sessionstore/test/browser_upgrade_backup.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://gre/modules/Services.jsm", this); +Cu.import("resource://gre/modules/osfile.jsm", this); +Cu.import("resource://gre/modules/Task.jsm", this); +Cu.import("resource://gre/modules/Preferences.jsm", this); + +const Paths = SessionFile.Paths; +const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID"; +const PREF_MAX_UPGRADE_BACKUPS = "browser.sessionstore.upgradeBackup.maxUpgradeBackups"; + +/** + * Prepares tests by retrieving the current platform's build ID, clearing the + * build where the last backup was created and creating arbitrary JSON data + * for a new backup. + */ +var prepareTest = Task.async(function* () { + let result = {}; + + result.buildID = Services.appinfo.platformBuildID; + Services.prefs.setCharPref(PREF_UPGRADE, ""); + result.contents = JSON.stringify({"browser_upgrade_backup.js": Math.random()}); + + return result; +}); + +/** + * Retrieves all upgrade backups and returns them in an array. + */ +var getUpgradeBackups = Task.async(function* () { + let iterator; + let backups = []; + let upgradeBackupPrefix = Paths.upgradeBackupPrefix; + + try { + iterator = new OS.File.DirectoryIterator(Paths.backups); + + // iterate over all files in the backup directory + yield iterator.forEach(function (file) { + // check the upgradeBackupPrefix + if (file.path.startsWith(Paths.upgradeBackupPrefix)) { + // the file is a backup + backups.push(file.path); + } + }, this); + } finally { + if (iterator) { + iterator.close(); + } + } + + // return results + return backups; +}); + +add_task(function* init() { + // Wait until initialization is complete + yield SessionStore.promiseInitialized; + yield SessionFile.wipe(); +}); + +add_task(function* test_upgrade_backup() { + let test = yield prepareTest(); + info("Let's check if we create an upgrade backup"); + yield OS.File.writeAtomic(Paths.clean, test.contents); + yield SessionFile.read(); // First call to read() initializes the SessionWorker + yield SessionFile.write(""); // First call to write() triggers the backup + + is(Services.prefs.getCharPref(PREF_UPGRADE), test.buildID, "upgrade backup should be set"); + + is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created"); + + let data = yield OS.File.read(Paths.upgradeBackup); + is(test.contents, (new TextDecoder()).decode(data), "upgrade backup contains the expected contents"); + + info("Let's check that we don't overwrite this upgrade backup"); + let newContents = JSON.stringify({"something else entirely": Math.random()}); + yield OS.File.writeAtomic(Paths.clean, newContents); + yield SessionFile.read(); // Reinitialize the SessionWorker + yield SessionFile.write(""); // Next call to write() shouldn't trigger the backup + data = yield OS.File.read(Paths.upgradeBackup); + is(test.contents, (new TextDecoder()).decode(data), "upgrade backup hasn't changed"); +}); + +add_task(function* test_upgrade_backup_removal() { + let test = yield prepareTest(); + let maxUpgradeBackups = Preferences.get(PREF_MAX_UPGRADE_BACKUPS, 3); + info("Let's see if we remove backups if there are too many"); + yield OS.File.writeAtomic(Paths.clean, test.contents); + + // if the nextUpgradeBackup already exists (from another test), remove it + if (OS.File.exists(Paths.nextUpgradeBackup)) { + yield OS.File.remove(Paths.nextUpgradeBackup); + } + + // create dummy backups + yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20080101010101", ""); + yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20090101010101", ""); + yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20100101010101", ""); + yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20110101010101", ""); + yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20120101010101", ""); + yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20130101010101", ""); + + // get currently existing backups + let backups = yield getUpgradeBackups(); + + // trigger new backup + yield SessionFile.read(); // First call to read() initializes the SessionWorker + yield SessionFile.write(""); // First call to write() triggers the backup and the cleanup + + // a new backup should have been created (and still exist) + is(Services.prefs.getCharPref(PREF_UPGRADE), test.buildID, "upgrade backup should be set"); + is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created"); + + // get currently existing backups and check their count + let newBackups = yield getUpgradeBackups(); + is(newBackups.length, maxUpgradeBackups, "expected number of backups are present after removing old backups"); + + // find all backups that were created during the last call to `SessionFile.write("");` + // ie, filter out all the backups that have already been present before the call + newBackups = newBackups.filter(function (backup) { + return backups.indexOf(backup) < 0; + }); + + // check that exactly one new backup was created + is(newBackups.length, 1, "one new backup was created that was not removed"); + + yield SessionFile.write(""); // Second call to write() should not trigger anything + + backups = yield getUpgradeBackups(); + is(backups.length, maxUpgradeBackups, "second call to SessionFile.write() didn't create or remove more backups"); +}); + diff --git a/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js b/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js new file mode 100644 index 000000000..781692909 --- /dev/null +++ b/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This test checks that closed private windows can't be restored + +function test() { + waitForExplicitFinish(); + + // Purging the list of closed windows + forgetClosedWindows(); + + // Load a private window, then close it + // and verify it doesn't get remembered for restoring + whenNewWindowLoaded({private: true}, function (win) { + info("The private window got loaded"); + win.addEventListener("SSWindowClosing", function onclosing() { + win.removeEventListener("SSWindowClosing", onclosing, false); + executeSoon(function () { + is(ss.getClosedWindowCount(), 0, + "The private window should not have been stored"); + }); + }, false); + BrowserTestUtils.closeWindow(win).then(finish); + }); +} diff --git a/browser/components/sessionstore/test/browser_windowStateContainer.js b/browser/components/sessionstore/test/browser_windowStateContainer.js new file mode 100644 index 000000000..beb838088 --- /dev/null +++ b/browser/components/sessionstore/test/browser_windowStateContainer.js @@ -0,0 +1,122 @@ +"use strict"; + +requestLongerTimeout(2); + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]] + }); +}); + +add_task(function* () { + let win = yield BrowserTestUtils.openNewBrowserWindow(); + + // Create 4 tabs with different userContextId. + for (let userContextId = 1; userContextId < 5; userContextId++) { + let tab = win.gBrowser.addTab("http://example.com/", {userContextId}); + yield promiseBrowserLoaded(tab.linkedBrowser); + yield TabStateFlusher.flush(tab.linkedBrowser); + } + + // Move the default tab of window to the end. + // We want the 1st tab to have non-default userContextId, so later when we + // restore into win2 we can test restore into an existing tab with different + // userContextId. + win.gBrowser.moveTabTo(win.gBrowser.tabs[0], win.gBrowser.tabs.length - 1); + + let winState = JSON.parse(ss.getWindowState(win)); + + for (let i = 0; i < 4; i++) { + Assert.equal(winState.windows[0].tabs[i].userContextId, i + 1, + "1st Window: tabs[" + i + "].userContextId should exist."); + } + + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + + // Create tabs with different userContextId, but this time we create them with + // fewer tabs and with different order with win. + for (let userContextId = 3; userContextId > 0; userContextId--) { + let tab = win2.gBrowser.addTab("http://example.com/", {userContextId}); + yield promiseBrowserLoaded(tab.linkedBrowser); + yield TabStateFlusher.flush(tab.linkedBrowser); + } + + ss.setWindowState(win2, JSON.stringify(winState), true); + + for (let i = 0; i < 4; i++) { + let browser = win2.gBrowser.tabs[i].linkedBrowser; + yield ContentTask.spawn(browser, { expectedId: i + 1 }, function* (args) { + Assert.equal(docShell.getOriginAttributes().userContextId, + args.expectedId, + "The docShell has the correct userContextId"); + + Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, + args.expectedId, + "The document has the correct userContextId"); + }); + } + + // Test the last tab, which doesn't have userContextId. + let browser = win2.gBrowser.tabs[4].linkedBrowser; + yield ContentTask.spawn(browser, { expectedId: 0 }, function* (args) { + Assert.equal(docShell.getOriginAttributes().userContextId, + args.expectedId, + "The docShell has the correct userContextId"); + + Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, + args.expectedId, + "The document has the correct userContextId"); + }); + + yield BrowserTestUtils.closeWindow(win); + yield BrowserTestUtils.closeWindow(win2); +}); + +add_task(function* () { + let win = yield BrowserTestUtils.openNewBrowserWindow(); + yield TabStateFlusher.flush(win.gBrowser.selectedBrowser); + + let tab = win.gBrowser.addTab("http://example.com/", { userContextId: 1 }); + yield promiseBrowserLoaded(tab.linkedBrowser); + yield TabStateFlusher.flush(tab.linkedBrowser); + + // win should have 1 default tab, and 1 container tab. + Assert.equal(win.gBrowser.tabs.length, 2, "win should have 2 tabs"); + + let winState = JSON.parse(ss.getWindowState(win)); + + for (let i = 0; i < 2; i++) { + Assert.equal(winState.windows[0].tabs[i].userContextId, i, + "1st Window: tabs[" + i + "].userContextId should be " + i); + } + + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + + let tab2 = win2.gBrowser.addTab("http://example.com/", { userContextId : 1 }); + yield promiseBrowserLoaded(tab2.linkedBrowser); + yield TabStateFlusher.flush(tab2.linkedBrowser); + + // Move the first normal tab to end, so the first tab of win2 will be a + // container tab. + win2.gBrowser.moveTabTo(win2.gBrowser.tabs[0], win2.gBrowser.tabs.length - 1); + yield TabStateFlusher.flush(win2.gBrowser.tabs[0].linkedBrowser); + + ss.setWindowState(win2, JSON.stringify(winState), true); + + for (let i = 0; i < 2; i++) { + let browser = win2.gBrowser.tabs[i].linkedBrowser; + yield ContentTask.spawn(browser, { expectedId: i }, function* (args) { + Assert.equal(docShell.getOriginAttributes().userContextId, + args.expectedId, + "The docShell has the correct userContextId"); + + Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, + args.expectedId, + "The document has the correct userContextId"); + }); + } + + yield BrowserTestUtils.closeWindow(win); + yield BrowserTestUtils.closeWindow(win2); +}); + diff --git a/browser/components/sessionstore/test/content-forms.js b/browser/components/sessionstore/test/content-forms.js new file mode 100644 index 000000000..da7bc9c08 --- /dev/null +++ b/browser/components/sessionstore/test/content-forms.js @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +/** + * This frame script is only loaded for sessionstore mochitests. It contains + * a bunch of utility functions used to test form data collection and + * restoration in remote browsers. + */ + +function queryElement(data) { + let frame = content; + if (data.hasOwnProperty("frame")) { + frame = content.frames[data.frame]; + } + + let doc = frame.document; + + if (data.hasOwnProperty("id")) { + return doc.getElementById(data.id); + } + + if (data.hasOwnProperty("selector")) { + return doc.querySelector(data.selector); + } + + if (data.hasOwnProperty("xpath")) { + let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE; + return doc.evaluate(data.xpath, doc, null, xptype, null).singleNodeValue; + } + + throw new Error("couldn't query element"); +} + +function dispatchUIEvent(input, type) { + let event = input.ownerDocument.createEvent("UIEvents"); + event.initUIEvent(type, true, true, input.ownerGlobal, 0); + input.dispatchEvent(event); +} + +function defineListener(type, cb) { + addMessageListener("ss-test:" + type, function ({data}) { + sendAsyncMessage("ss-test:" + type, cb(data)); + }); +} + +defineListener("sendKeyEvent", function (data) { + let frame = content; + if (data.hasOwnProperty("frame")) { + frame = content.frames[data.frame]; + } + + let ifreq = frame.QueryInterface(Ci.nsIInterfaceRequestor); + let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils); + + let keyCode = data.key.charCodeAt(0); + let charCode = Ci.nsIDOMKeyEvent.DOM_VK_A + keyCode - "a".charCodeAt(0); + + utils.sendKeyEvent("keydown", keyCode, charCode, null); + utils.sendKeyEvent("keypress", keyCode, charCode, null); + utils.sendKeyEvent("keyup", keyCode, charCode, null); +}); + +defineListener("getInnerHTML", function (data) { + return queryElement(data).innerHTML; +}); + +defineListener("getTextContent", function (data) { + return queryElement(data).textContent; +}); + +defineListener("getInputValue", function (data) { + return queryElement(data).value; +}); + +defineListener("setInputValue", function (data) { + let input = queryElement(data); + input.value = data.value; + dispatchUIEvent(input, "input"); +}); + +defineListener("getInputChecked", function (data) { + return queryElement(data).checked; +}); + +defineListener("setInputChecked", function (data) { + let input = queryElement(data); + input.checked = data.checked; + dispatchUIEvent(input, "change"); +}); + +defineListener("getSelectedIndex", function (data) { + return queryElement(data).selectedIndex; +}); + +defineListener("setSelectedIndex", function (data) { + let input = queryElement(data); + input.selectedIndex = data.index; + dispatchUIEvent(input, "change"); +}); + +defineListener("getMultipleSelected", function (data) { + let input = queryElement(data); + return Array.map(input.options, (opt, idx) => idx) + .filter(idx => input.options[idx].selected); +}); + +defineListener("setMultipleSelected", function (data) { + let input = queryElement(data); + Array.forEach(input.options, (opt, idx) => opt.selected = data.indices.indexOf(idx) > -1); + dispatchUIEvent(input, "change"); +}); + +defineListener("getFileNameArray", function (data) { + return queryElement(data).mozGetFileNameArray(); +}); + +defineListener("setFileNameArray", function (data) { + let input = queryElement(data); + input.mozSetFileNameArray(data.names, data.names.length); + dispatchUIEvent(input, "input"); +}); + +defineListener("setFormElementValues", function (data) { + for (let elem of content.document.forms[0].elements) { + elem.value = data.value; + dispatchUIEvent(elem, "input"); + } +}); diff --git a/browser/components/sessionstore/test/content.js b/browser/components/sessionstore/test/content.js new file mode 100644 index 000000000..e815a6783 --- /dev/null +++ b/browser/components/sessionstore/test/content.js @@ -0,0 +1,222 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var Cu = Components.utils; +var Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this); +var gFrameTree = new FrameTree(this); + +function executeSoon(callback) { + Services.tm.mainThread.dispatch(callback, Components.interfaces.nsIThread.DISPATCH_NORMAL); +} + +gFrameTree.addObserver({ + onFrameTreeReset: function () { + sendAsyncMessage("ss-test:onFrameTreeReset"); + }, + + onFrameTreeCollected: function () { + sendAsyncMessage("ss-test:onFrameTreeCollected"); + } +}); + +var historyListener = { + OnHistoryNewEntry: function () { + sendAsyncMessage("ss-test:OnHistoryNewEntry"); + }, + + OnHistoryGoBack: function () { + sendAsyncMessage("ss-test:OnHistoryGoBack"); + return true; + }, + + OnHistoryGoForward: function () { + sendAsyncMessage("ss-test:OnHistoryGoForward"); + return true; + }, + + OnHistoryGotoIndex: function () { + sendAsyncMessage("ss-test:OnHistoryGotoIndex"); + return true; + }, + + OnHistoryPurge: function () { + sendAsyncMessage("ss-test:OnHistoryPurge"); + return true; + }, + + OnHistoryReload: function () { + sendAsyncMessage("ss-test:OnHistoryReload"); + return true; + }, + + OnHistoryReplaceEntry: function () { + sendAsyncMessage("ss-test:OnHistoryReplaceEntry"); + }, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsISHistoryListener, + Ci.nsISupportsWeakReference + ]) +}; + +var {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation); +if (sessionHistory) { + sessionHistory.addSHistoryListener(historyListener); +} + +/** + * This frame script is only loaded for sessionstore mochitests. It enables us + * to modify and query docShell data when running with multiple processes. + */ + +addEventListener("hashchange", function () { + sendAsyncMessage("ss-test:hashchange"); +}); + +addMessageListener("ss-test:purgeDomainData", function ({data: domain}) { + Services.obs.notifyObservers(null, "browser:purge-domain-data", domain); + content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData")); +}); + +addMessageListener("ss-test:getStyleSheets", function (msg) { + let sheets = content.document.styleSheets; + let titles = Array.map(sheets, ss => [ss.title, ss.disabled]); + sendSyncMessage("ss-test:getStyleSheets", titles); +}); + +addMessageListener("ss-test:enableStyleSheetsForSet", function (msg) { + let sheets = content.document.styleSheets; + let change = false; + for (let i = 0; i < sheets.length; i++) { + if (sheets[i].disabled != (msg.data.indexOf(sheets[i].title) == -1)) { + change = true; + break; + } + } + function observer() { + Services.obs.removeObserver(observer, "style-sheet-applicable-state-changed"); + + // It's possible our observer will run before the one in + // content-sessionStore.js. Therefore, we run ours a little + // later. + executeSoon(() => sendAsyncMessage("ss-test:enableStyleSheetsForSet")); + } + if (change) { + // We don't want to reply until content-sessionStore.js has seen + // the change. + Services.obs.addObserver(observer, "style-sheet-applicable-state-changed", false); + + content.document.enableStyleSheetsForSet(msg.data); + } else { + sendAsyncMessage("ss-test:enableStyleSheetsForSet"); + } +}); + +addMessageListener("ss-test:enableSubDocumentStyleSheetsForSet", function (msg) { + let iframe = content.document.getElementById(msg.data.id); + iframe.contentDocument.enableStyleSheetsForSet(msg.data.set); + sendAsyncMessage("ss-test:enableSubDocumentStyleSheetsForSet"); +}); + +addMessageListener("ss-test:getAuthorStyleDisabled", function (msg) { + let {authorStyleDisabled} = + docShell.contentViewer; + sendSyncMessage("ss-test:getAuthorStyleDisabled", authorStyleDisabled); +}); + +addMessageListener("ss-test:setAuthorStyleDisabled", function (msg) { + let markupDocumentViewer = + docShell.contentViewer; + markupDocumentViewer.authorStyleDisabled = msg.data; + sendSyncMessage("ss-test:setAuthorStyleDisabled"); +}); + +addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) { + let loadContext = + docShell.QueryInterface(Ci.nsILoadContext); + loadContext.usePrivateBrowsing = msg.data; + sendAsyncMessage("ss-test:setUsePrivateBrowsing"); +}); + +addMessageListener("ss-test:getScrollPosition", function (msg) { + let frame = content; + if (msg.data.hasOwnProperty("frame")) { + frame = content.frames[msg.data.frame]; + } + let {scrollX: x, scrollY: y} = frame; + sendAsyncMessage("ss-test:getScrollPosition", {x: x, y: y}); +}); + +addMessageListener("ss-test:setScrollPosition", function (msg) { + let frame = content; + let {x, y} = msg.data; + if (msg.data.hasOwnProperty("frame")) { + frame = content.frames[msg.data.frame]; + } + frame.scrollTo(x, y); + + frame.addEventListener("scroll", function onScroll(event) { + if (frame.document == event.target) { + frame.removeEventListener("scroll", onScroll); + sendAsyncMessage("ss-test:setScrollPosition"); + } + }); +}); + +addMessageListener("ss-test:createDynamicFrames", function ({data}) { + function createIFrame(rows) { + let frames = content.document.getElementById(data.id); + frames.setAttribute("rows", rows); + + let frame = content.document.createElement("frame"); + frame.setAttribute("src", data.url); + frames.appendChild(frame); + } + + addEventListener("DOMContentLoaded", function onContentLoaded(event) { + if (content.document == event.target) { + removeEventListener("DOMContentLoaded", onContentLoaded, true); + // DOMContentLoaded is fired right after we finished parsing the document. + createIFrame("33%, 33%, 33%"); + } + }, true); + + addEventListener("load", function onLoad(event) { + if (content.document == event.target) { + removeEventListener("load", onLoad, true); + + // Creating this frame on the same tick as the load event + // means that it must not be included in the frame tree. + createIFrame("25%, 25%, 25%, 25%"); + } + }, true); + + sendAsyncMessage("ss-test:createDynamicFrames"); +}); + +addMessageListener("ss-test:removeLastFrame", function ({data}) { + let frames = content.document.getElementById(data.id); + frames.lastElementChild.remove(); + sendAsyncMessage("ss-test:removeLastFrame"); +}); + +addMessageListener("ss-test:mapFrameTree", function (msg) { + let result = gFrameTree.map(frame => ({href: frame.location.href})); + sendAsyncMessage("ss-test:mapFrameTree", result); +}); + +addMessageListener("ss-test:click", function ({data}) { + content.document.getElementById(data.id).click(); + sendAsyncMessage("ss-test:click"); +}); + +addEventListener("load", function(event) { + let subframe = event.target != content.document; + sendAsyncMessage("ss-test:loadEvent", {subframe: subframe, url: event.target.documentURI}); +}, true); diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js new file mode 100644 index 000000000..5a8c5dbfc --- /dev/null +++ b/browser/components/sessionstore/test/head.js @@ -0,0 +1,564 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 TAB_STATE_NEEDS_RESTORE = 1; +const TAB_STATE_RESTORING = 2; + +const ROOT = getRootDirectory(gTestPath); +const HTTPROOT = ROOT.replace("chrome://mochitests/content/", "http://example.com/"); +const FRAME_SCRIPTS = [ + ROOT + "content.js", + ROOT + "content-forms.js" +]; + +var mm = Cc["@mozilla.org/globalmessagemanager;1"] + .getService(Ci.nsIMessageListenerManager); + +for (let script of FRAME_SCRIPTS) { + mm.loadFrameScript(script, true); +} + +registerCleanupFunction(() => { + for (let script of FRAME_SCRIPTS) { + mm.removeDelayedFrameScript(script, true); + } +}); + +const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +const {SessionStore} = Cu.import("resource:///modules/sessionstore/SessionStore.jsm", {}); +const {SessionSaver} = Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", {}); +const {SessionFile} = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}); +const {TabState} = Cu.import("resource:///modules/sessionstore/TabState.jsm", {}); +const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + +const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + +// Some tests here assume that all restored tabs are loaded without waiting for +// the user to bring them to the foreground. We ensure this by resetting the +// related preference (see the "firefox.js" defaults file for details). +Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false); +registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); +}); + +// Obtain access to internals +Services.prefs.setBoolPref("browser.sessionstore.debug", true); +registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.sessionstore.debug"); +}); + + +// This kicks off the search service used on about:home and allows the +// session restore tests to be run standalone without triggering errors. +Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs; + +function provideWindow(aCallback, aURL, aFeatures) { + function callbackSoon(aWindow) { + executeSoon(function executeCallbackSoon() { + aCallback(aWindow); + }); + } + + let win = openDialog(getBrowserURL(), "", aFeatures || "chrome,all,dialog=no", aURL || "about:blank"); + whenWindowLoaded(win, function onWindowLoaded(aWin) { + if (!aURL) { + info("Loaded a blank window."); + callbackSoon(aWin); + return; + } + + aWin.gBrowser.selectedBrowser.addEventListener("load", function selectedBrowserLoadListener() { + aWin.gBrowser.selectedBrowser.removeEventListener("load", selectedBrowserLoadListener, true); + callbackSoon(aWin); + }, true); + }); +} + +// This assumes that tests will at least have some state/entries +function waitForBrowserState(aState, aSetStateCallback) { + if (typeof aState == "string") { + aState = JSON.parse(aState); + } + if (typeof aState != "object") { + throw new TypeError("Argument must be an object or a JSON representation of an object"); + } + let windows = [window]; + let tabsRestored = 0; + let expectedTabsRestored = 0; + let expectedWindows = aState.windows.length; + let windowsOpen = 1; + let listening = false; + let windowObserving = false; + let restoreHiddenTabs = Services.prefs.getBoolPref( + "browser.sessionstore.restore_hidden_tabs"); + + aState.windows.forEach(function (winState) { + winState.tabs.forEach(function (tabState) { + if (restoreHiddenTabs || !tabState.hidden) + expectedTabsRestored++; + }); + }); + + // There must be only hidden tabs and restoreHiddenTabs = false. We still + // expect one of them to be restored because it gets shown automatically. + if (!expectedTabsRestored) + expectedTabsRestored = 1; + + function onSSTabRestored(aEvent) { + if (++tabsRestored == expectedTabsRestored) { + // Remove the event listener from each window + windows.forEach(function(win) { + win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true); + }); + listening = false; + info("running " + aSetStateCallback.name); + executeSoon(aSetStateCallback); + } + } + + // Used to add our listener to further windows so we can catch SSTabRestored + // coming from them when creating a multi-window state. + function windowObserver(aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + let newWindow = aSubject.QueryInterface(Ci.nsIDOMWindow); + newWindow.addEventListener("load", function() { + newWindow.removeEventListener("load", arguments.callee, false); + + if (++windowsOpen == expectedWindows) { + Services.ww.unregisterNotification(windowObserver); + windowObserving = false; + } + + // Track this window so we can remove the progress listener later + windows.push(newWindow); + // Add the progress listener + newWindow.gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true); + }, false); + } + } + + // We only want to register the notification if we expect more than 1 window + if (expectedWindows > 1) { + registerCleanupFunction(function() { + if (windowObserving) { + Services.ww.unregisterNotification(windowObserver); + } + }); + windowObserving = true; + Services.ww.registerNotification(windowObserver); + } + + registerCleanupFunction(function() { + if (listening) { + windows.forEach(function(win) { + win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true); + }); + } + }); + // Add the event listener for this window as well. + listening = true; + gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true); + + // Ensure setBrowserState() doesn't remove the initial tab. + gBrowser.selectedTab = gBrowser.tabs[0]; + + // Finally, call setBrowserState + ss.setBrowserState(JSON.stringify(aState)); +} + +function promiseBrowserState(aState) { + return new Promise(resolve => waitForBrowserState(aState, resolve)); +} + +function promiseTabState(tab, state) { + if (typeof(state) != "string") { + state = JSON.stringify(state); + } + + let promise = promiseTabRestored(tab); + ss.setTabState(tab, state); + return promise; +} + +/** + * Wait for a content -> chrome message. + */ +function promiseContentMessage(browser, name) { + let mm = browser.messageManager; + + return new Promise(resolve => { + function removeListener() { + mm.removeMessageListener(name, listener); + } + + function listener(msg) { + removeListener(); + resolve(msg.data); + } + + mm.addMessageListener(name, listener); + registerCleanupFunction(removeListener); + }); +} + +function waitForTopic(aTopic, aTimeout, aCallback) { + let observing = false; + function removeObserver() { + if (!observing) + return; + Services.obs.removeObserver(observer, aTopic); + observing = false; + } + + let timeout = setTimeout(function () { + removeObserver(); + aCallback(false); + }, aTimeout); + + function observer(aSubject, aTopic, aData) { + removeObserver(); + timeout = clearTimeout(timeout); + executeSoon(() => aCallback(true)); + } + + registerCleanupFunction(function() { + removeObserver(); + if (timeout) { + clearTimeout(timeout); + } + }); + + observing = true; + Services.obs.addObserver(observer, aTopic, false); +} + +/** + * Wait until session restore has finished collecting its data and is + * has written that data ("sessionstore-state-write-complete"). + * + * @param {function} aCallback If sessionstore-state-write-complete is sent + * within buffering interval + 100 ms, the callback is passed |true|, + * otherwise, it is passed |false|. + */ +function waitForSaveState(aCallback) { + let timeout = 100 + + Services.prefs.getIntPref("browser.sessionstore.interval"); + return waitForTopic("sessionstore-state-write-complete", timeout, aCallback); +} +function promiseSaveState() { + return new Promise(resolve => { + waitForSaveState(isSuccessful => { + if (!isSuccessful) { + throw new Error("timeout"); + } + + resolve(); + }); + }); +} +function forceSaveState() { + return SessionSaver.run(); +} + +function promiseRecoveryFileContents() { + let promise = forceSaveState(); + return promise.then(function() { + return OS.File.read(SessionFile.Paths.recovery, { encoding: "utf-8" }); + }); +} + +var promiseForEachSessionRestoreFile = Task.async(function*(cb) { + for (let key of SessionFile.Paths.loadOrder) { + let data = ""; + try { + data = yield OS.File.read(SessionFile.Paths[key], { encoding: "utf-8" }); + } catch (ex) { + // Ignore missing files + if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) { + throw ex; + } + } + cb(data, key); + } +}); + +function promiseBrowserLoaded(aBrowser, ignoreSubFrames = true, wantLoad = null) { + return BrowserTestUtils.browserLoaded(aBrowser, !ignoreSubFrames, wantLoad); +} + +function whenWindowLoaded(aWindow, aCallback = next) { + aWindow.addEventListener("load", function windowLoadListener() { + aWindow.removeEventListener("load", windowLoadListener, false); + executeSoon(function executeWhenWindowLoaded() { + aCallback(aWindow); + }); + }, false); +} +function promiseWindowLoaded(aWindow) { + return new Promise(resolve => whenWindowLoaded(aWindow, resolve)); +} + +var gUniqueCounter = 0; +function r() { + return Date.now() + "-" + (++gUniqueCounter); +} + +function* BrowserWindowIterator() { + let windowsEnum = Services.wm.getEnumerator("navigator:browser"); + while (windowsEnum.hasMoreElements()) { + let currentWindow = windowsEnum.getNext(); + if (!currentWindow.closed) { + yield currentWindow; + } + } +} + +var gWebProgressListener = { + _callback: null, + + setCallback: function (aCallback) { + if (!this._callback) { + window.gBrowser.addTabsProgressListener(this); + } + this._callback = aCallback; + }, + + unsetCallback: function () { + if (this._callback) { + this._callback = null; + window.gBrowser.removeTabsProgressListener(this); + } + }, + + onStateChange: function (aBrowser, aWebProgress, aRequest, + aStateFlags, aStatus) { + if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { + this._callback(aBrowser); + } + } +}; + +registerCleanupFunction(function () { + gWebProgressListener.unsetCallback(); +}); + +var gProgressListener = { + _callback: null, + + setCallback: function (callback) { + Services.obs.addObserver(this, "sessionstore-debug-tab-restored", false); + this._callback = callback; + }, + + unsetCallback: function () { + if (this._callback) { + this._callback = null; + Services.obs.removeObserver(this, "sessionstore-debug-tab-restored"); + } + }, + + observe: function (browser, topic, data) { + gProgressListener.onRestored(browser); + }, + + onRestored: function (browser) { + if (browser.__SS_restoreState == TAB_STATE_RESTORING) { + let args = [browser].concat(gProgressListener._countTabs()); + gProgressListener._callback.apply(gProgressListener, args); + } + }, + + _countTabs: function () { + let needsRestore = 0, isRestoring = 0, wasRestored = 0; + + for (let win of BrowserWindowIterator()) { + for (let i = 0; i < win.gBrowser.tabs.length; i++) { + let browser = win.gBrowser.tabs[i].linkedBrowser; + if (!browser.__SS_restoreState) + wasRestored++; + else if (browser.__SS_restoreState == TAB_STATE_RESTORING) + isRestoring++; + else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) + needsRestore++; + } + } + return [needsRestore, isRestoring, wasRestored]; + } +}; + +registerCleanupFunction(function () { + gProgressListener.unsetCallback(); +}); + +// Close all but our primary window. +function promiseAllButPrimaryWindowClosed() { + let windows = []; + for (let win of BrowserWindowIterator()) { + if (win != window) { + windows.push(win); + } + } + + return Promise.all(windows.map(BrowserTestUtils.closeWindow)); +} + +// Forget all closed windows. +function forgetClosedWindows() { + while (ss.getClosedWindowCount() > 0) { + ss.forgetClosedWindow(0); + } +} + +/** + * When opening a new window it is not sufficient to wait for its load event. + * We need to use whenDelayedStartupFinshed() here as the browser window's + * delayedStartup() routine is executed one tick after the window's load event + * has been dispatched. browser-delayed-startup-finished might be deferred even + * further if parts of the window's initialization process take more time than + * expected (e.g. reading a big session state from disk). + */ +function whenNewWindowLoaded(aOptions, aCallback) { + let features = ""; + let url = "about:blank"; + + if (aOptions && aOptions.private || false) { + features = ",private"; + url = "about:privatebrowsing"; + } + + let win = openDialog(getBrowserURL(), "", "chrome,all,dialog=no" + features, url); + let delayedStartup = promiseDelayedStartupFinished(win); + + let browserLoaded = new Promise(resolve => { + if (url == "about:blank") { + resolve(); + return; + } + + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + let browser = win.gBrowser.selectedBrowser; + promiseBrowserLoaded(browser).then(resolve); + }); + }); + + Promise.all([delayedStartup, browserLoaded]).then(() => aCallback(win)); +} +function promiseNewWindowLoaded(aOptions) { + return new Promise(resolve => whenNewWindowLoaded(aOptions, resolve)); +} + +/** + * This waits for the browser-delayed-startup-finished notification of a given + * window. It indicates that the windows has loaded completely and is ready to + * be used for testing. + */ +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + executeSoon(aCallback); + } + }, "browser-delayed-startup-finished", false); +} +function promiseDelayedStartupFinished(aWindow) { + return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve)); +} + +function promiseEvent(element, eventType, isCapturing = false) { + return new Promise(resolve => { + element.addEventListener(eventType, function listener(event) { + element.removeEventListener(eventType, listener, isCapturing); + resolve(event); + }, isCapturing); + }); +} + +function promiseTabRestored(tab) { + return promiseEvent(tab, "SSTabRestored"); +} + +function promiseTabRestoring(tab) { + return promiseEvent(tab, "SSTabRestoring"); +} + +function sendMessage(browser, name, data = {}) { + browser.messageManager.sendAsyncMessage(name, data); + return promiseContentMessage(browser, name); +} + +// This creates list of functions that we will map to their corresponding +// ss-test:* messages names. Those will be sent to the frame script and +// be used to read and modify form data. +const FORM_HELPERS = [ + "getTextContent", + "getInputValue", "setInputValue", + "getInputChecked", "setInputChecked", + "getSelectedIndex", "setSelectedIndex", + "getMultipleSelected", "setMultipleSelected", + "getFileNameArray", "setFileNameArray", +]; + +for (let name of FORM_HELPERS) { + let msg = "ss-test:" + name; + this[name] = (browser, data) => sendMessage(browser, msg, data); +} + +// Removes the given tab immediately and returns a promise that resolves when +// all pending status updates (messages) of the closing tab have been received. +function promiseRemoveTab(tab) { + return BrowserTestUtils.removeTab(tab); +} + +// Write DOMSessionStorage data to the given browser. +function modifySessionStorage(browser, data, options = {}) { + return ContentTask.spawn(browser, [data, options], function* ([data, options]) { + let frame = content; + if (options && "frameIndex" in options) { + frame = content.frames[options.frameIndex]; + } + + let keys = new Set(Object.keys(data)); + let storage = frame.sessionStorage; + + return new Promise(resolve => { + addEventListener("MozSessionStorageChanged", function onStorageChanged(event) { + if (event.storageArea == storage) { + keys.delete(event.key); + } + + if (keys.size == 0) { + removeEventListener("MozSessionStorageChanged", onStorageChanged, true); + resolve(); + } + }, true); + + for (let key of keys) { + frame.sessionStorage[key] = data[key]; + } + }); + }); +} + +function pushPrefs(...aPrefs) { + return new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve); + }); +} + +function popPrefs() { + return new Promise(resolve => { + SpecialPowers.popPrefEnv(resolve); + }); +} + +function* checkScroll(tab, expected, msg) { + let browser = tab.linkedBrowser; + yield TabStateFlusher.flush(browser); + + let scroll = JSON.parse(ss.getTabState(tab)).scroll || null; + is(JSON.stringify(scroll), JSON.stringify(expected), msg); +} diff --git a/browser/components/sessionstore/test/restore_redirect_http.html b/browser/components/sessionstore/test/restore_redirect_http.html new file mode 100644 index 000000000..e69de29bb diff --git a/browser/components/sessionstore/test/restore_redirect_http.html^headers^ b/browser/components/sessionstore/test/restore_redirect_http.html^headers^ new file mode 100644 index 000000000..533bda36f --- /dev/null +++ b/browser/components/sessionstore/test/restore_redirect_http.html^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved Temporarily +Location: restore_redirect_target.html diff --git a/browser/components/sessionstore/test/restore_redirect_js.html b/browser/components/sessionstore/test/restore_redirect_js.html new file mode 100644 index 000000000..1f5f0e54c --- /dev/null +++ b/browser/components/sessionstore/test/restore_redirect_js.html @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/browser/components/sessionstore/test/restore_redirect_target.html b/browser/components/sessionstore/test/restore_redirect_target.html new file mode 100644 index 000000000..6c8b3aae5 --- /dev/null +++ b/browser/components/sessionstore/test/restore_redirect_target.html @@ -0,0 +1,8 @@ + + + + +Test page + +Test page + diff --git a/browser/components/sessionstore/test/unit/.eslintrc.js b/browser/components/sessionstore/test/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/browser/components/sessionstore/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json new file mode 100644 index 000000000..928de6a39 --- /dev/null +++ b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json @@ -0,0 +1 @@ +{"profile-after-change":true,"final-ui-startup":true,"sessionstore-windows-restored":true,"quit-application-granted":true,"quit-application":true,"sessionstore-final-state-write-complete":true,"profile-change-net-teardown":true,"profile-change-teardown":true,"profile-before-change":true} \ No newline at end of file diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js new file mode 100644 index 000000000..a8c3ff2ff --- /dev/null +++ b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js @@ -0,0 +1,3 @@ +{ + "windows": // invalid json +} diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_valid.js b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js new file mode 100644 index 000000000..f9511f29f --- /dev/null +++ b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js @@ -0,0 +1,3 @@ +{ + "windows": [] +} \ No newline at end of file diff --git a/browser/components/sessionstore/test/unit/head.js b/browser/components/sessionstore/test/unit/head.js new file mode 100644 index 000000000..b62856012 --- /dev/null +++ b/browser/components/sessionstore/test/unit/head.js @@ -0,0 +1,32 @@ +var Cu = Components.utils; +var Cc = Components.classes; +var Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +// Call a function once initialization of SessionStartup is complete +function afterSessionStartupInitialization(cb) { + do_print("Waiting for session startup initialization"); + let observer = function() { + try { + do_print("Session startup initialization observed"); + Services.obs.removeObserver(observer, "sessionstore-state-finalized"); + cb(); + } catch (ex) { + do_throw(ex); + } + }; + + // We need the Crash Monitor initialized for sessionstartup to run + // successfully. + Components.utils.import("resource://gre/modules/CrashMonitor.jsm"); + CrashMonitor.init(); + + // Start sessionstartup initialization. + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsIObserver); + Services.obs.addObserver(startup, "final-ui-startup", false); + Services.obs.addObserver(startup, "quit-application", false); + Services.obs.notifyObservers(null, "final-ui-startup", ""); + Services.obs.addObserver(observer, "sessionstore-state-finalized", false); +}; diff --git a/browser/components/sessionstore/test/unit/test_backup_once.js b/browser/components/sessionstore/test/unit/test_backup_once.js new file mode 100644 index 000000000..fff34ad58 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_backup_once.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); +var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); +var {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +var {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {}); + +var File = OS.File; +var Paths; +var SessionFile; + +// We need a XULAppInfo to initialize SessionFile +Cu.import("resource://testing-common/AppInfo.jsm", this); +updateAppInfo({ + name: "SessionRestoreTest", + ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}", + version: "1", + platformVersion: "", +}); + +function run_test() { + run_next_test(); +} + +add_task(function* init() { + // Make sure that we have a profile before initializing SessionFile + let profd = do_get_profile(); + SessionFile = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}).SessionFile; + Paths = SessionFile.Paths; + + + let source = do_get_file("data/sessionstore_valid.js"); + source.copyTo(profd, "sessionstore.js"); + + // Finish initialization of SessionFile + yield SessionFile.read(); +}); + +var pathStore; +var pathBackup; +var decoder; + +function promise_check_exist(path, shouldExist) { + return Task.spawn(function*() { + do_print("Ensuring that " + path + (shouldExist?" exists":" does not exist")); + if ((yield OS.File.exists(path)) != shouldExist) { + throw new Error("File " + path + " should " + (shouldExist?"exist":"not exist")); + } + }); +} + +function promise_check_contents(path, expect) { + return Task.spawn(function*() { + do_print("Checking whether " + path + " has the right contents"); + let actual = yield OS.File.read(path, { encoding: "utf-8"}); + Assert.deepEqual(JSON.parse(actual), expect, `File ${path} contains the expected data.`); + }); +} + +function generateFileContents(id) { + let url = `http://example.com/test_backup_once#${id}_${Math.random()}`; + return {windows: [{tabs: [{entries: [{url}], index: 1}]}]} +} + +// Write to the store, and check that it creates: +// - $Path.recovery with the new data +// - $Path.nextUpgradeBackup with the old data +add_task(function* test_first_write_backup() { + let initial_content = generateFileContents("initial"); + let new_content = generateFileContents("test_1"); + + do_print("Before the first write, none of the files should exist"); + yield promise_check_exist(Paths.backups, false); + + yield File.makeDir(Paths.backups); + yield File.writeAtomic(Paths.clean, JSON.stringify(initial_content), { encoding: "utf-8" }); + yield SessionFile.write(new_content); + + do_print("After first write, a few files should have been created"); + yield promise_check_exist(Paths.backups, true); + yield promise_check_exist(Paths.clean, false); + yield promise_check_exist(Paths.cleanBackup, true); + yield promise_check_exist(Paths.recovery, true); + yield promise_check_exist(Paths.recoveryBackup, false); + yield promise_check_exist(Paths.nextUpgradeBackup, true); + + yield promise_check_contents(Paths.recovery, new_content); + yield promise_check_contents(Paths.nextUpgradeBackup, initial_content); +}); + +// Write to the store again, and check that +// - $Path.clean is not written +// - $Path.recovery contains the new data +// - $Path.recoveryBackup contains the previous data +add_task(function* test_second_write_no_backup() { + let new_content = generateFileContents("test_2"); + let previous_backup_content = yield File.read(Paths.recovery, { encoding: "utf-8" }); + previous_backup_content = JSON.parse(previous_backup_content); + + yield OS.File.remove(Paths.cleanBackup); + + yield SessionFile.write(new_content); + + yield promise_check_exist(Paths.backups, true); + yield promise_check_exist(Paths.clean, false); + yield promise_check_exist(Paths.cleanBackup, false); + yield promise_check_exist(Paths.recovery, true); + yield promise_check_exist(Paths.nextUpgradeBackup, true); + + yield promise_check_contents(Paths.recovery, new_content); + yield promise_check_contents(Paths.recoveryBackup, previous_backup_content); +}); + +// Make sure that we create $Paths.clean and remove $Paths.recovery* +// upon shutdown +add_task(function* test_shutdown() { + let output = generateFileContents("test_3"); + + yield File.writeAtomic(Paths.recovery, "I should disappear"); + yield File.writeAtomic(Paths.recoveryBackup, "I should also disappear"); + + yield SessionWorker.post("write", [output, { isFinalWrite: true, performShutdownCleanup: true}]); + + do_check_false((yield File.exists(Paths.recovery))); + do_check_false((yield File.exists(Paths.recoveryBackup))); + yield promise_check_contents(Paths.clean, output); +}); diff --git a/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js new file mode 100644 index 000000000..c7d8b03ed --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The primary purpose of this test is to ensure that + * the sessionstore component records information about + * corrupted backup files into a histogram. + */ + +"use strict"; +Cu.import("resource://gre/modules/osfile.jsm", this); + +const Telemetry = Services.telemetry; +const Path = OS.Path; +const HistogramId = "FX_SESSION_RESTORE_ALL_FILES_CORRUPT"; + +// Prepare the session file. +var profd = do_get_profile(); +Cu.import("resource:///modules/sessionstore/SessionFile.jsm", this); + +/** + * A utility function for resetting the histogram and the contents + * of the backup directory. + */ +function reset_session(backups = {}) { + + // Reset the histogram. + Telemetry.getHistogramById(HistogramId).clear(); + + // Reset the contents of the backups directory + OS.File.makeDir(SessionFile.Paths.backups); + for (let key of SessionFile.Paths.loadOrder) { + if (backups.hasOwnProperty(key)) { + OS.File.copy(backups[key], SessionFile.Paths[key]); + } else { + OS.File.remove(SessionFile.Paths[key]); + } + } +} + +/** + * In order to use FX_SESSION_RESTORE_ALL_FILES_CORRUPT histogram + * it has to be registered in "toolkit/components/telemetry/Histograms.json". + * This test ensures that the histogram is registered and empty. + */ +add_task(function* test_ensure_histogram_exists_and_empty() { + let s = Telemetry.getHistogramById(HistogramId).snapshot(); + Assert.equal(s.sum, 0, "Initially, the sum of probes is 0"); +}); + +/** + * Makes sure that the histogram is negatively updated when no + * backup files are present. + */ +add_task(function* test_no_files_exist() { + // No session files are available to SessionFile. + reset_session(); + + yield SessionFile.read(); + // Checking if the histogram is updated negatively + let h = Telemetry.getHistogramById(HistogramId); + let s = h.snapshot(); + Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket."); + Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket."); +}); + +/** + * Makes sure that the histogram is negatively updated when at least one + * backup file is not corrupted. + */ +add_task(function* test_one_file_valid() { + // Corrupting some backup files. + let invalidSession = "data/sessionstore_invalid.js"; + let validSession = "data/sessionstore_valid.js"; + reset_session({ + clean : invalidSession, + cleanBackup: validSession, + recovery: invalidSession, + recoveryBackup: invalidSession + }); + + yield SessionFile.read(); + // Checking if the histogram is updated negatively. + let h = Telemetry.getHistogramById(HistogramId); + let s = h.snapshot(); + Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket."); + Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket."); +}); + +/** + * Makes sure that the histogram is positively updated when all + * backup files are corrupted. + */ +add_task(function* test_all_files_corrupt() { + // Corrupting all backup files. + let invalidSession = "data/sessionstore_invalid.js"; + reset_session({ + clean : invalidSession, + cleanBackup: invalidSession, + recovery: invalidSession, + recoveryBackup: invalidSession + }); + + yield SessionFile.read(); + // Checking if the histogram is positively updated. + let h = Telemetry.getHistogramById(HistogramId); + let s = h.snapshot(); + Assert.equal(s.counts[1], 1, "One probe for the 'true' bucket."); + Assert.equal(s.counts[0], 0, "No probes in the 'false' bucket."); +}); + +function run_test() { + run_next_test(); +} diff --git a/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js new file mode 100644 index 000000000..b99e566e9 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js @@ -0,0 +1,127 @@ +"use strict"; + +/** + * This test ensures that we correctly clean up the session state before + * writing to disk a last time on shutdown. For now it only tests that each + * tab's shistory is capped to a maximum number of preceding and succeeding + * entries. + */ + +const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); +const {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +const {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {}); + +const profd = do_get_profile(); +const {SessionFile} = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}); +const {Paths} = SessionFile; + +const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); +const {File} = OS; + +const MAX_ENTRIES = 9; +const URL = "http://example.com/#"; + +// We need a XULAppInfo to initialize SessionFile +Cu.import("resource://testing-common/AppInfo.jsm", this); +updateAppInfo({ + name: "SessionRestoreTest", + ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}", + version: "1", + platformVersion: "", +}); + +add_task(function* setup() { + let source = do_get_file("data/sessionstore_valid.js"); + source.copyTo(profd, "sessionstore.js"); + + // Finish SessionFile initialization. + yield SessionFile.read(); + + // Reset prefs on cleanup. + do_register_cleanup(() => { + Services.prefs.clearUserPref("browser.sessionstore.max_serialize_back"); + Services.prefs.clearUserPref("browser.sessionstore.max_serialize_forward"); + }); +}); + +function createSessionState(index) { + // Generate the tab state entries and set the one-based + // tab-state index to the middle session history entry. + let tabState = {entries: [], index}; + for (let i = 0; i < MAX_ENTRIES; i++) { + tabState.entries.push({url: URL + i}); + } + + return {windows: [{tabs: [tabState]}]}; +} + +function* setMaxBackForward(back, fwd) { + Services.prefs.setIntPref("browser.sessionstore.max_serialize_back", back); + Services.prefs.setIntPref("browser.sessionstore.max_serialize_forward", fwd); + yield SessionFile.read(); +} + +function* writeAndParse(state, path, options = {}) { + yield SessionWorker.post("write", [state, options]); + return JSON.parse(yield File.read(path, {encoding: "utf-8"})); +} + +add_task(function* test_shistory_cap_none() { + let state = createSessionState(5); + + // Don't limit the number of shistory entries. + yield setMaxBackForward(-1, -1); + + // Check that no caps are applied. + let diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true}); + Assert.deepEqual(state, diskState, "no cap applied"); +}); + +add_task(function* test_shistory_cap_middle() { + let state = createSessionState(5); + yield setMaxBackForward(2, 3); + + // Cap is only applied on clean shutdown. + let diskState = yield writeAndParse(state, Paths.recovery); + Assert.deepEqual(state, diskState, "no cap applied"); + + // Check that the right number of shistory entries was discarded + // and the shistory index updated accordingly. + diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true}); + let tabState = state.windows[0].tabs[0]; + tabState.entries = tabState.entries.slice(2, 8); + tabState.index = 3; + Assert.deepEqual(state, diskState, "cap applied"); +}); + +add_task(function* test_shistory_cap_lower_bound() { + let state = createSessionState(1); + yield setMaxBackForward(5, 5); + + // Cap is only applied on clean shutdown. + let diskState = yield writeAndParse(state, Paths.recovery); + Assert.deepEqual(state, diskState, "no cap applied"); + + // Check that the right number of shistory entries was discarded. + diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true}); + let tabState = state.windows[0].tabs[0]; + tabState.entries = tabState.entries.slice(0, 6); + Assert.deepEqual(state, diskState, "cap applied"); +}); + +add_task(function* test_shistory_cap_upper_bound() { + let state = createSessionState(MAX_ENTRIES); + yield setMaxBackForward(5, 5); + + // Cap is only applied on clean shutdown. + let diskState = yield writeAndParse(state, Paths.recovery); + Assert.deepEqual(state, diskState, "no cap applied"); + + // Check that the right number of shistory entries was discarded + // and the shistory index updated accordingly. + diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true}); + let tabState = state.windows[0].tabs[0]; + tabState.entries = tabState.entries.slice(3); + tabState.index = 6; + Assert.deepEqual(state, diskState, "cap applied"); +}); diff --git a/browser/components/sessionstore/test/unit/test_startup_invalid_session.js b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js new file mode 100644 index 000000000..9f6df8585 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let profd = do_get_profile(); + + let sourceSession = do_get_file("data/sessionstore_invalid.js"); + sourceSession.copyTo(profd, "sessionstore.js"); + + let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json"); + sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json"); + + do_test_pending(); + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + + afterSessionStartupInitialization(function cb() { + do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION); + do_test_finished(); + }); +} diff --git a/browser/components/sessionstore/test/unit/test_startup_nosession_async.js b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js new file mode 100644 index 000000000..5185b02d6 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +// Test nsISessionStartup.sessionType in the following scenario: +// - no sessionstore.js; +// - the session store has been loaded, so no need to go +// through the synchronous fallback + +function run_test() { + do_get_profile(); + // Initialize the profile (the session startup uses it) + + do_test_pending(); + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + + afterSessionStartupInitialization(function cb() { + do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION); + do_test_finished(); + }); +} \ No newline at end of file diff --git a/browser/components/sessionstore/test/unit/test_startup_session_async.js b/browser/components/sessionstore/test/unit/test_startup_session_async.js new file mode 100644 index 000000000..459acf885 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_startup_session_async.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +// Test nsISessionStartup.sessionType in the following scenario: +// - valid sessionstore.js; +// - valid sessionCheckpoints.json with all checkpoints; +// - the session store has been loaded + +function run_test() { + let profd = do_get_profile(); + + let sourceSession = do_get_file("data/sessionstore_valid.js"); + sourceSession.copyTo(profd, "sessionstore.js"); + + let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json"); + sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json"); + + do_test_pending(); + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + + afterSessionStartupInitialization(function cb() { + do_check_eq(startup.sessionType, Ci.nsISessionStartup.DEFER_SESSION); + do_test_finished(); + }); +} diff --git a/browser/components/sessionstore/test/unit/xpcshell.ini b/browser/components/sessionstore/test/unit/xpcshell.ini new file mode 100644 index 000000000..09980f877 --- /dev/null +++ b/browser/components/sessionstore/test/unit/xpcshell.ini @@ -0,0 +1,16 @@ +[DEFAULT] +head = head.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' +support-files = + data/sessionCheckpoints_all.json + data/sessionstore_invalid.js + data/sessionstore_valid.js + +[test_backup_once.js] +[test_histogram_corrupt_files.js] +[test_shutdown_cleanup.js] +[test_startup_nosession_async.js] +[test_startup_session_async.js] +[test_startup_invalid_session.js] -- cgit v1.2.3