From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- docshell/base/BaseHistory.cpp | 246 + docshell/base/BaseHistory.h | 85 + docshell/base/BrowsingContext.cpp | 3832 +++++ docshell/base/BrowsingContext.h | 1458 ++ docshell/base/BrowsingContextGroup.cpp | 579 + docshell/base/BrowsingContextGroup.h | 318 + docshell/base/BrowsingContextWebProgress.cpp | 432 + docshell/base/BrowsingContextWebProgress.h | 98 + docshell/base/CanonicalBrowsingContext.cpp | 3069 ++++ docshell/base/CanonicalBrowsingContext.h | 601 + docshell/base/ChildProcessChannelListener.cpp | 61 + docshell/base/ChildProcessChannelListener.h | 54 + docshell/base/IHistory.h | 169 + docshell/base/LoadContext.cpp | 236 + docshell/base/LoadContext.h | 68 + docshell/base/SerializedLoadContext.cpp | 87 + docshell/base/SerializedLoadContext.h | 96 + docshell/base/SyncedContext.h | 402 + docshell/base/SyncedContextInlines.h | 358 + docshell/base/URIFixup.sys.mjs | 1303 ++ docshell/base/WindowContext.cpp | 662 + docshell/base/WindowContext.h | 399 + docshell/base/crashtests/1257730-1.html | 25 + docshell/base/crashtests/1331295.html | 25 + docshell/base/crashtests/1341657.html | 18 + docshell/base/crashtests/1584467.html | 12 + docshell/base/crashtests/1614211-1.html | 15 + docshell/base/crashtests/1617315-1.html | 8 + docshell/base/crashtests/1667491.html | 16 + docshell/base/crashtests/1667491_1.html | 21 + docshell/base/crashtests/1672873.html | 6 + docshell/base/crashtests/1690169-1.html | 11 + docshell/base/crashtests/1753136.html | 2 + docshell/base/crashtests/1804803.html | 13 + docshell/base/crashtests/1804803.sjs | 18 + docshell/base/crashtests/369126-1.html | 16 + docshell/base/crashtests/40929-1-inner.html | 14 + docshell/base/crashtests/40929-1.html | 6 + docshell/base/crashtests/430124-1.html | 5 + docshell/base/crashtests/430628-1.html | 8 + docshell/base/crashtests/432114-1.html | 8 + docshell/base/crashtests/432114-2.html | 21 + docshell/base/crashtests/436900-1-inner.html | 21 + docshell/base/crashtests/436900-1.html | 8 + docshell/base/crashtests/436900-2-inner.html | 21 + docshell/base/crashtests/436900-2.html | 8 + docshell/base/crashtests/443655.html | 15 + docshell/base/crashtests/500328-1.html | 17 + docshell/base/crashtests/514779-1.xhtml | 9 + docshell/base/crashtests/614499-1.html | 20 + docshell/base/crashtests/678872-1.html | 36 + docshell/base/crashtests/914521.html | 32 + docshell/base/crashtests/crashtests.list | 25 + docshell/base/crashtests/file_432114-2.xhtml | 1 + docshell/base/moz.build | 130 + docshell/base/nsAboutRedirector.cpp | 299 + docshell/base/nsAboutRedirector.h | 26 + docshell/base/nsCTooltipTextProvider.h | 15 + docshell/base/nsDSURIContentListener.cpp | 297 + docshell/base/nsDSURIContentListener.h | 100 + docshell/base/nsDocShell.cpp | 13877 +++++++++++++++++++ docshell/base/nsDocShell.h | 1388 ++ docshell/base/nsDocShellEditorData.cpp | 139 + docshell/base/nsDocShellEditorData.h | 66 + docshell/base/nsDocShellEnumerator.cpp | 85 + docshell/base/nsDocShellEnumerator.h | 39 + docshell/base/nsDocShellLoadState.cpp | 1266 ++ docshell/base/nsDocShellLoadState.h | 573 + docshell/base/nsDocShellLoadTypes.h | 205 + docshell/base/nsDocShellTelemetryUtils.cpp | 206 + docshell/base/nsDocShellTelemetryUtils.h | 22 + docshell/base/nsDocShellTreeOwner.cpp | 1340 ++ docshell/base/nsDocShellTreeOwner.h | 111 + docshell/base/nsIContentViewer.idl | 318 + docshell/base/nsIContentViewerEdit.idl | 36 + docshell/base/nsIDocShell.idl | 796 ++ docshell/base/nsIDocShellTreeItem.idl | 171 + docshell/base/nsIDocShellTreeOwner.idl | 113 + docshell/base/nsIDocumentLoaderFactory.idl | 39 + docshell/base/nsILoadContext.idl | 148 + docshell/base/nsILoadURIDelegate.idl | 35 + docshell/base/nsIPrivacyTransitionObserver.idl | 11 + docshell/base/nsIReflowObserver.idl | 31 + docshell/base/nsIRefreshURI.idl | 52 + docshell/base/nsIScrollObserver.h | 45 + docshell/base/nsITooltipListener.idl | 44 + docshell/base/nsITooltipTextProvider.idl | 44 + docshell/base/nsIURIFixup.idl | 205 + docshell/base/nsIWebNavigation.idl | 415 + docshell/base/nsIWebNavigationInfo.idl | 55 + docshell/base/nsIWebPageDescriptor.idl | 30 + docshell/base/nsPingListener.cpp | 345 + docshell/base/nsPingListener.h | 48 + docshell/base/nsRefreshTimer.cpp | 49 + docshell/base/nsRefreshTimer.h | 39 + docshell/base/nsWebNavigationInfo.cpp | 64 + docshell/base/nsWebNavigationInfo.h | 34 + docshell/base/timeline/AbstractTimelineMarker.cpp | 72 + docshell/base/timeline/AbstractTimelineMarker.h | 71 + .../base/timeline/AutoGlobalTimelineMarker.cpp | 39 + docshell/base/timeline/AutoGlobalTimelineMarker.h | 48 + .../base/timeline/AutoRestyleTimelineMarker.cpp | 51 + docshell/base/timeline/AutoRestyleTimelineMarker.h | 30 + docshell/base/timeline/AutoTimelineMarker.cpp | 48 + docshell/base/timeline/AutoTimelineMarker.h | 46 + docshell/base/timeline/CompositeTimelineMarker.h | 31 + docshell/base/timeline/ConsoleTimelineMarker.h | 53 + docshell/base/timeline/DocLoadingTimelineMarker.h | 38 + docshell/base/timeline/EventTimelineMarker.h | 40 + docshell/base/timeline/JavascriptTimelineMarker.h | 96 + docshell/base/timeline/LayerTimelineMarker.h | 47 + docshell/base/timeline/MarkersStorage.h | 40 + docshell/base/timeline/MessagePortTimelineMarker.h | 46 + docshell/base/timeline/ObservedDocShell.cpp | 171 + docshell/base/timeline/ObservedDocShell.h | 55 + docshell/base/timeline/RestyleTimelineMarker.h | 37 + docshell/base/timeline/TimelineConsumers.cpp | 202 + docshell/base/timeline/TimelineConsumers.h | 113 + docshell/base/timeline/TimelineMarker.cpp | 66 + docshell/base/timeline/TimelineMarker.h | 47 + docshell/base/timeline/TimelineMarkerEnums.h | 18 + docshell/base/timeline/TimestampTimelineMarker.h | 36 + docshell/base/timeline/WorkerTimelineMarker.h | 44 + docshell/base/timeline/moz.build | 44 + docshell/base/timeline/readme.md | 97 + docshell/build/components.conf | 192 + docshell/build/moz.build | 25 + docshell/build/nsDocShellCID.h | 59 + docshell/build/nsDocShellModule.cpp | 25 + docshell/build/nsDocShellModule.h | 20 + docshell/moz.build | 48 + docshell/shistory/ChildSHistory.cpp | 294 + docshell/shistory/ChildSHistory.h | 149 + docshell/shistory/SessionHistoryEntry.cpp | 1828 +++ docshell/shistory/SessionHistoryEntry.h | 510 + docshell/shistory/moz.build | 42 + docshell/shistory/nsIBFCacheEntry.idl | 16 + docshell/shistory/nsISHEntry.idl | 476 + docshell/shistory/nsISHistory.idl | 291 + docshell/shistory/nsISHistoryListener.idl | 88 + docshell/shistory/nsSHEntry.cpp | 1131 ++ docshell/shistory/nsSHEntry.h | 72 + docshell/shistory/nsSHEntryShared.cpp | 343 + docshell/shistory/nsSHEntryShared.h | 219 + docshell/shistory/nsSHistory.cpp | 2410 ++++ docshell/shistory/nsSHistory.h | 343 + docshell/test/browser/Bug1622420Child.sys.mjs | 9 + docshell/test/browser/Bug422543Child.sys.mjs | 98 + docshell/test/browser/browser.ini | 237 + .../browser_alternate_fixup_middle_click_link.js | 59 + .../browser/browser_backforward_restore_scroll.js | 54 + .../browser/browser_backforward_userinteraction.js | 374 + .../browser_backforward_userinteraction_about.js | 67 + ..._backforward_userinteraction_systemprincipal.js | 112 + .../test/browser/browser_badCertDomainFixup.js | 92 + .../test/browser/browser_bfcache_copycommand.js | 98 + .../test/browser/browser_browsingContext-01.js | 180 + .../test/browser/browser_browsingContext-02.js | 235 + .../browser/browser_browsingContext-embedder.js | 156 + ...wsingContext-getAllBrowsingContextsInSubtree.js | 53 + .../browser_browsingContext-getWindowByName.js | 34 + .../browser/browser_browsingContext-webProgress.js | 226 + .../browser/browser_browsing_context_attached.js | 179 + .../browser/browser_browsing_context_discarded.js | 65 + docshell/test/browser/browser_bug1206879.js | 50 + ...ser_bug1309900_crossProcessHistoryNavigation.js | 54 + docshell/test/browser/browser_bug1328501.js | 69 + docshell/test/browser/browser_bug1347823.js | 91 + docshell/test/browser/browser_bug134911.js | 57 + .../browser_bug1415918_beforeunload_options.js | 162 + docshell/test/browser/browser_bug1543077-3.js | 46 + docshell/test/browser/browser_bug1594938.js | 100 + docshell/test/browser/browser_bug1622420.js | 31 + docshell/test/browser/browser_bug1648464-1.js | 46 + docshell/test/browser/browser_bug1673702.js | 27 + docshell/test/browser/browser_bug1674464.js | 38 + docshell/test/browser/browser_bug1688368-1.js | 24 + docshell/test/browser/browser_bug1691153.js | 73 + docshell/test/browser/browser_bug1705872.js | 74 + docshell/test/browser/browser_bug1716290-1.js | 24 + docshell/test/browser/browser_bug1716290-2.js | 24 + docshell/test/browser/browser_bug1716290-3.js | 24 + docshell/test/browser/browser_bug1716290-4.js | 24 + docshell/test/browser/browser_bug1719178.js | 34 + docshell/test/browser/browser_bug1736248-1.js | 34 + docshell/test/browser/browser_bug1757005.js | 73 + docshell/test/browser/browser_bug1769189.js | 32 + docshell/test/browser/browser_bug1798780.js | 52 + docshell/test/browser/browser_bug234628-1.js | 47 + docshell/test/browser/browser_bug234628-10.js | 46 + docshell/test/browser/browser_bug234628-11.js | 46 + docshell/test/browser/browser_bug234628-2.js | 49 + docshell/test/browser/browser_bug234628-3.js | 47 + docshell/test/browser/browser_bug234628-4.js | 46 + docshell/test/browser/browser_bug234628-5.js | 46 + docshell/test/browser/browser_bug234628-6.js | 47 + docshell/test/browser/browser_bug234628-8.js | 18 + docshell/test/browser/browser_bug234628-9.js | 18 + docshell/test/browser/browser_bug349769.js | 79 + docshell/test/browser/browser_bug388121-1.js | 22 + docshell/test/browser/browser_bug388121-2.js | 74 + docshell/test/browser/browser_bug420605.js | 131 + docshell/test/browser/browser_bug422543.js | 253 + docshell/test/browser/browser_bug441169.js | 44 + docshell/test/browser/browser_bug503832.js | 76 + docshell/test/browser/browser_bug554155.js | 33 + docshell/test/browser/browser_bug655270.js | 62 + docshell/test/browser/browser_bug655273.js | 56 + docshell/test/browser/browser_bug670318.js | 146 + docshell/test/browser/browser_bug673087-1.js | 46 + docshell/test/browser/browser_bug673087-2.js | 36 + docshell/test/browser/browser_bug673467.js | 62 + docshell/test/browser/browser_bug852909.js | 35 + docshell/test/browser/browser_bug92473.js | 70 + .../browser_click_link_within_view_source.js | 78 + .../browser_cross_process_csp_inheritance.js | 125 + .../browser_csp_sandbox_no_script_js_uri.js | 55 + docshell/test/browser/browser_csp_uir.js | 89 + .../browser_dataURI_unique_opaque_origin.js | 30 + .../test/browser/browser_data_load_inherit_csp.js | 110 + .../test/browser/browser_fall_back_to_https.js | 73 + .../browser_frameloader_swap_with_bfcache.js | 37 + ...owser_history_triggeringprincipal_viewsource.js | 88 + docshell/test/browser/browser_isInitialDocument.js | 319 + docshell/test/browser/browser_loadURI_postdata.js | 42 + .../test/browser/browser_multiple_pushState.js | 25 + .../test/browser/browser_onbeforeunload_frame.js | 45 + .../browser/browser_onbeforeunload_navigation.js | 165 + .../test/browser/browser_onbeforeunload_parent.js | 48 + docshell/test/browser/browser_onunload_stop.js | 23 + docshell/test/browser/browser_overlink.js | 27 + .../test/browser/browser_platform_emulation.js | 70 + .../test/browser/browser_search_notification.js | 49 + .../browser/browser_tab_replace_while_loading.js | 83 + docshell/test/browser/browser_tab_touch_events.js | 75 + .../browser_targetTopLevelLinkClicksToBlank.js | 285 + .../test/browser/browser_timelineMarkers-01.js | 46 + .../test/browser/browser_timelineMarkers-02.js | 16 + .../browser/browser_timelineMarkers-frame-02.js | 185 + .../browser/browser_title_in_session_history.js | 63 + docshell/test/browser/browser_ua_emulation.js | 71 + .../browser/browser_uriFixupAlternateRedirects.js | 66 + .../test/browser/browser_uriFixupIntegration.js | 104 + .../browser_viewsource_chrome_to_content.js | 20 + .../test/browser/browser_viewsource_multipart.js | 44 + docshell/test/browser/dummy_iframe_page.html | 8 + docshell/test/browser/dummy_page.html | 6 + docshell/test/browser/favicon_bug655270.ico | Bin 0 -> 1406 bytes .../browser/file_backforward_restore_scroll.html | 10 + .../file_backforward_restore_scroll.html^headers^ | 1 + docshell/test/browser/file_basic_multipart.sjs | 24 + docshell/test/browser/file_bug1046022.html | 54 + docshell/test/browser/file_bug1206879.html | 9 + docshell/test/browser/file_bug1328501.html | 27 + docshell/test/browser/file_bug1328501_frame.html | 4 + .../test/browser/file_bug1328501_framescript.js | 38 + docshell/test/browser/file_bug1543077-3-child.html | 11 + docshell/test/browser/file_bug1543077-3.html | 16 + docshell/test/browser/file_bug1622420.html | 1 + docshell/test/browser/file_bug1648464-1-child.html | 13 + docshell/test/browser/file_bug1648464-1.html | 18 + docshell/test/browser/file_bug1673702.json | 1 + .../test/browser/file_bug1673702.json^headers^ | 1 + docshell/test/browser/file_bug1688368-1.sjs | 44 + docshell/test/browser/file_bug1691153.html | 27 + docshell/test/browser/file_bug1716290-1.sjs | 21 + docshell/test/browser/file_bug1716290-2.sjs | 18 + docshell/test/browser/file_bug1716290-3.sjs | 17 + docshell/test/browser/file_bug1716290-4.sjs | 17 + docshell/test/browser/file_bug1736248-1.html | 4 + docshell/test/browser/file_bug234628-1-child.html | 12 + docshell/test/browser/file_bug234628-1.html | 17 + .../test/browser/file_bug234628-10-child.xhtml | 4 + docshell/test/browser/file_bug234628-10.html | 17 + .../test/browser/file_bug234628-11-child.xhtml | 4 + .../browser/file_bug234628-11-child.xhtml^headers^ | 1 + docshell/test/browser/file_bug234628-11.html | 17 + docshell/test/browser/file_bug234628-2-child.html | 12 + docshell/test/browser/file_bug234628-2.html | 17 + docshell/test/browser/file_bug234628-3-child.html | 13 + docshell/test/browser/file_bug234628-3.html | 18 + docshell/test/browser/file_bug234628-4-child.html | 12 + docshell/test/browser/file_bug234628-4.html | 18 + docshell/test/browser/file_bug234628-5-child.html | Bin 0 -> 498 bytes docshell/test/browser/file_bug234628-5.html | 18 + docshell/test/browser/file_bug234628-6-child.html | Bin 0 -> 540 bytes .../browser/file_bug234628-6-child.html^headers^ | 1 + docshell/test/browser/file_bug234628-6.html | 18 + docshell/test/browser/file_bug234628-8-child.html | 12 + docshell/test/browser/file_bug234628-8.html | 17 + docshell/test/browser/file_bug234628-9-child.html | 12 + docshell/test/browser/file_bug234628-9.html | Bin 0 -> 740 bytes docshell/test/browser/file_bug420605.html | 31 + docshell/test/browser/file_bug503832.html | 35 + docshell/test/browser/file_bug655270.html | 11 + docshell/test/browser/file_bug670318.html | 23 + docshell/test/browser/file_bug673087-1-child.html | 13 + docshell/test/browser/file_bug673087-1.html | Bin 0 -> 432 bytes .../test/browser/file_bug673087-1.html^headers^ | 1 + docshell/test/browser/file_bug673087-2.html | 2 + docshell/test/browser/file_bug852909.pdf | Bin 0 -> 1568 bytes docshell/test/browser/file_bug852909.png | Bin 0 -> 94 bytes .../file_click_link_within_view_source.html | 6 + .../file_cross_process_csp_inheritance.html | 11 + .../browser/file_csp_sandbox_no_script_js_uri.html | 11 + ...file_csp_sandbox_no_script_js_uri.html^headers^ | 1 + docshell/test/browser/file_csp_uir.html | 11 + docshell/test/browser/file_csp_uir_dummy.html | 1 + .../test/browser/file_data_load_inherit_csp.html | 11 + docshell/test/browser/file_multiple_pushState.html | 20 + docshell/test/browser/file_onbeforeunload_0.html | 9 + docshell/test/browser/file_onbeforeunload_1.html | 9 + docshell/test/browser/file_onbeforeunload_2.html | 10 + docshell/test/browser/file_onbeforeunload_3.html | 9 + docshell/test/browser/file_open_about_blank.html | 2 + docshell/test/browser/file_slow_load.sjs | 8 + docshell/test/browser/frame-head.js | 114 + docshell/test/browser/head.js | 258 + .../test/browser/head_browser_onbeforeunload.js | 271 + docshell/test/browser/onload_message.html | 25 + docshell/test/browser/onpageshow_message.html | 41 + docshell/test/browser/overlink_test.html | 7 + docshell/test/browser/print_postdata.sjs | 25 + docshell/test/browser/redirect_to_example.sjs | 5 + docshell/test/browser/test-form_sjis.html | 24 + docshell/test/chrome/112564_nocache.html | 10 + docshell/test/chrome/112564_nocache.html^headers^ | 1 + docshell/test/chrome/215405_nocache.html | 14 + docshell/test/chrome/215405_nocache.html^headers^ | 1 + docshell/test/chrome/215405_nostore.html | 14 + docshell/test/chrome/215405_nostore.html^headers^ | 1 + docshell/test/chrome/582176_dummy.html | 1 + docshell/test/chrome/582176_xml.xml | 2 + docshell/test/chrome/582176_xslt.xsl | 8 + docshell/test/chrome/662200a.html | 8 + docshell/test/chrome/662200b.html | 8 + docshell/test/chrome/662200c.html | 7 + docshell/test/chrome/89419.html | 7 + docshell/test/chrome/92598_nostore.html | 10 + docshell/test/chrome/92598_nostore.html^headers^ | 1 + docshell/test/chrome/DocShellHelpers.sys.mjs | 74 + docshell/test/chrome/allowContentRetargeting.sjs | 7 + docshell/test/chrome/blue.png | Bin 0 -> 2745 bytes docshell/test/chrome/bug112564_window.xhtml | 86 + docshell/test/chrome/bug113934_window.xhtml | 165 + docshell/test/chrome/bug215405_window.xhtml | 177 + docshell/test/chrome/bug293235.html | 13 + docshell/test/chrome/bug293235_p2.html | 8 + docshell/test/chrome/bug293235_window.xhtml | 118 + docshell/test/chrome/bug294258_testcase.html | 43 + docshell/test/chrome/bug294258_window.xhtml | 72 + docshell/test/chrome/bug298622_window.xhtml | 135 + docshell/test/chrome/bug301397_1.html | 9 + docshell/test/chrome/bug301397_2.html | 10 + docshell/test/chrome/bug301397_3.html | 10 + docshell/test/chrome/bug301397_4.html | 9 + docshell/test/chrome/bug301397_window.xhtml | 218 + docshell/test/chrome/bug303267.html | 23 + docshell/test/chrome/bug303267_window.xhtml | 83 + docshell/test/chrome/bug311007_window.xhtml | 204 + docshell/test/chrome/bug321671_window.xhtml | 128 + docshell/test/chrome/bug360511_case1.html | 15 + docshell/test/chrome/bug360511_case2.html | 15 + docshell/test/chrome/bug360511_window.xhtml | 127 + docshell/test/chrome/bug364461_window.xhtml | 253 + docshell/test/chrome/bug396519_window.xhtml | 132 + docshell/test/chrome/bug396649_window.xhtml | 119 + docshell/test/chrome/bug449778_window.xhtml | 107 + docshell/test/chrome/bug449780_window.xhtml | 83 + docshell/test/chrome/bug454235-subframe.xhtml | 7 + docshell/test/chrome/bug582176_window.xhtml | 74 + docshell/test/chrome/bug608669.xhtml | 14 + docshell/test/chrome/bug662200_window.xhtml | 119 + docshell/test/chrome/bug690056_window.xhtml | 171 + docshell/test/chrome/bug846906.html | 10 + docshell/test/chrome/bug89419.sjs | 12 + docshell/test/chrome/bug89419_window.xhtml | 69 + docshell/test/chrome/bug909218.html | 11 + docshell/test/chrome/bug909218.js | 2 + docshell/test/chrome/bug92598_window.xhtml | 89 + docshell/test/chrome/chrome.ini | 107 + docshell/test/chrome/docshell_helpers.js | 759 + .../file_viewsource_forbidden_in_iframe.html | 11 + docshell/test/chrome/gen_template.pl | 39 + docshell/test/chrome/generic.html | 12 + docshell/test/chrome/mozFrameType_window.xhtml | 49 + docshell/test/chrome/red.png | Bin 0 -> 82 bytes docshell/test/chrome/test.template.txt | 41 + .../test/chrome/test_allowContentRetargeting.html | 76 + docshell/test/chrome/test_bug112564.xhtml | 37 + docshell/test/chrome/test_bug113934.xhtml | 29 + docshell/test/chrome/test_bug215405.xhtml | 37 + docshell/test/chrome/test_bug293235.xhtml | 38 + docshell/test/chrome/test_bug294258.xhtml | 38 + docshell/test/chrome/test_bug298622.xhtml | 38 + docshell/test/chrome/test_bug301397.xhtml | 38 + docshell/test/chrome/test_bug303267.xhtml | 39 + docshell/test/chrome/test_bug311007.xhtml | 42 + docshell/test/chrome/test_bug321671.xhtml | 38 + docshell/test/chrome/test_bug360511.xhtml | 39 + docshell/test/chrome/test_bug364461.xhtml | 43 + docshell/test/chrome/test_bug396519.xhtml | 28 + docshell/test/chrome/test_bug396649.xhtml | 41 + docshell/test/chrome/test_bug428288.html | 37 + docshell/test/chrome/test_bug449778.xhtml | 29 + docshell/test/chrome/test_bug449780.xhtml | 29 + docshell/test/chrome/test_bug453650.xhtml | 120 + docshell/test/chrome/test_bug454235.xhtml | 40 + docshell/test/chrome/test_bug456980.xhtml | 29 + docshell/test/chrome/test_bug565388.xhtml | 79 + docshell/test/chrome/test_bug582176.xhtml | 38 + docshell/test/chrome/test_bug608669.xhtml | 80 + docshell/test/chrome/test_bug662200.xhtml | 38 + docshell/test/chrome/test_bug690056.xhtml | 26 + docshell/test/chrome/test_bug789773.xhtml | 67 + docshell/test/chrome/test_bug846906.xhtml | 94 + docshell/test/chrome/test_bug89419.xhtml | 38 + docshell/test/chrome/test_bug909218.html | 117 + docshell/test/chrome/test_bug92598.xhtml | 37 + docshell/test/chrome/test_docRedirect.sjs | 6 + docshell/test/chrome/test_docRedirect.xhtml | 91 + docshell/test/chrome/test_mozFrameType.xhtml | 42 + .../test_open_and_immediately_close_opener.html | 54 + .../test_viewsource_forbidden_in_iframe.xhtml | 159 + docshell/test/chrome/window.template.txt | 44 + .../file_child_navigation_by_location.html | 1 + .../iframesandbox/file_marquee_event_handlers.html | 17 + ...ile_other_auxiliary_navigation_by_location.html | 15 + .../file_our_auxiliary_navigation_by_location.html | 15 + .../file_parent_navigation_by_location.html | 18 + .../file_sibling_navigation_by_location.html | 15 + .../file_top_navigation_by_location.html | 20 + .../file_top_navigation_by_location_exotic.html | 27 + .../file_top_navigation_by_user_activation.html | 27 + ...e_top_navigation_by_user_activation_iframe.html | 32 + docshell/test/iframesandbox/mochitest.ini | 30 + .../test_child_navigation_by_location.html | 91 + .../iframesandbox/test_marquee_event_handlers.html | 95 + ...est_other_auxiliary_navigation_by_location.html | 80 + .../test_our_auxiliary_navigation_by_location.html | 84 + .../test_parent_navigation_by_location.html | 75 + .../test_sibling_navigation_by_location.html | 78 + .../test_top_navigation_by_location.html | 167 + .../test_top_navigation_by_location_exotic.html | 204 + .../test_top_navigation_by_user_activation.html | 74 + docshell/test/mochitest/bug1422334_redirect.html | 3 + .../mochitest/bug1422334_redirect.html^headers^ | 2 + docshell/test/mochitest/bug404548-subframe.html | 7 + .../test/mochitest/bug404548-subframe_window.html | 1 + docshell/test/mochitest/bug413310-post.sjs | 10 + docshell/test/mochitest/bug413310-subframe.html | 7 + docshell/test/mochitest/bug529119-window.html | 7 + docshell/test/mochitest/bug530396-noref.sjs | 22 + docshell/test/mochitest/bug530396-subframe.html | 7 + .../test/mochitest/bug570341_recordevents.html | 21 + docshell/test/mochitest/bug668513_redirect.html | 1 + .../mochitest/bug668513_redirect.html^headers^ | 2 + docshell/test/mochitest/bug691547_frame.html | 12 + docshell/test/mochitest/clicker.html | 7 + docshell/test/mochitest/double_submit.sjs | 79 + docshell/test/mochitest/dummy_page.html | 6 + .../file_anchor_scroll_after_document_open.html | 15 + .../test/mochitest/file_bfcache_plus_hash_1.html | 24 + .../test/mochitest/file_bfcache_plus_hash_2.html | 17 + docshell/test/mochitest/file_bug1121701_1.html | 29 + docshell/test/mochitest/file_bug1121701_2.html | 23 + docshell/test/mochitest/file_bug1151421.html | 19 + docshell/test/mochitest/file_bug1186774.html | 1 + docshell/test/mochitest/file_bug1450164.html | 16 + docshell/test/mochitest/file_bug1729662.html | 8 + docshell/test/mochitest/file_bug1740516_1.html | 29 + .../test/mochitest/file_bug1740516_1_inner.html | 15 + docshell/test/mochitest/file_bug1740516_2.html | 11 + docshell/test/mochitest/file_bug1741132.html | 29 + docshell/test/mochitest/file_bug1742865.sjs | 77 + docshell/test/mochitest/file_bug1742865_outer.sjs | 25 + docshell/test/mochitest/file_bug1743353.html | 37 + docshell/test/mochitest/file_bug1747033.sjs | 110 + docshell/test/mochitest/file_bug1773192_1.html | 13 + docshell/test/mochitest/file_bug1773192_2.html | 13 + docshell/test/mochitest/file_bug1773192_3.sjs | 3 + docshell/test/mochitest/file_bug385434_1.html | 29 + docshell/test/mochitest/file_bug385434_2.html | 26 + docshell/test/mochitest/file_bug385434_3.html | 22 + docshell/test/mochitest/file_bug475636.sjs | 97 + docshell/test/mochitest/file_bug509055.html | 9 + docshell/test/mochitest/file_bug511449.html | 6 + docshell/test/mochitest/file_bug540462.html | 25 + docshell/test/mochitest/file_bug580069_1.html | 8 + docshell/test/mochitest/file_bug580069_2.sjs | 8 + docshell/test/mochitest/file_bug590573_1.html | 7 + docshell/test/mochitest/file_bug590573_2.html | 8 + docshell/test/mochitest/file_bug598895_1.html | 1 + docshell/test/mochitest/file_bug598895_2.html | 1 + docshell/test/mochitest/file_bug634834.html | 5 + docshell/test/mochitest/file_bug637644_1.html | 1 + docshell/test/mochitest/file_bug637644_2.html | 1 + docshell/test/mochitest/file_bug640387.html | 26 + docshell/test/mochitest/file_bug653741.html | 13 + docshell/test/mochitest/file_bug660404 | 13 + docshell/test/mochitest/file_bug660404-1.html | 12 + docshell/test/mochitest/file_bug660404^headers^ | 1 + docshell/test/mochitest/file_bug662170.html | 13 + docshell/test/mochitest/file_bug668513.html | 101 + docshell/test/mochitest/file_bug669671.sjs | 17 + docshell/test/mochitest/file_bug675587.html | 1 + docshell/test/mochitest/file_bug680257.html | 16 + docshell/test/mochitest/file_bug703855.html | 2 + docshell/test/mochitest/file_bug728939.html | 3 + .../test/mochitest/file_close_onpagehide1.html | 5 + .../test/mochitest/file_close_onpagehide2.html | 5 + docshell/test/mochitest/file_compressed_multipart | Bin 0 -> 111 bytes .../mochitest/file_compressed_multipart^headers^ | 2 + .../file_content_javascript_loads_frame.html | 17 + .../file_content_javascript_loads_root.html | 42 + .../mochitest/file_form_restoration_no_store.html | 38 + .../file_form_restoration_no_store.html^headers^ | 1 + .../test/mochitest/file_framedhistoryframes.html | 16 + .../test/mochitest/file_load_during_reload.html | 12 + .../file_pushState_after_document_open.html | 11 + docshell/test/mochitest/file_redirect_history.html | 18 + docshell/test/mochitest/form_submit.sjs | 40 + docshell/test/mochitest/form_submit_redirect.sjs | 15 + docshell/test/mochitest/historyframes.html | 176 + docshell/test/mochitest/mochitest.ini | 209 + docshell/test/mochitest/ping.html | 6 + docshell/test/mochitest/start_historyframe.html | 1 + .../test_anchor_scroll_after_document_open.html | 55 + .../test/mochitest/test_bfcache_plus_hash.html | 153 + docshell/test/mochitest/test_bug1045096.html | 29 + docshell/test/mochitest/test_bug1121701.html | 108 + docshell/test/mochitest/test_bug1151421.html | 61 + docshell/test/mochitest/test_bug1186774.html | 51 + docshell/test/mochitest/test_bug1422334.html | 40 + docshell/test/mochitest/test_bug1450164.html | 31 + docshell/test/mochitest/test_bug1507702.html | 57 + docshell/test/mochitest/test_bug1645781.html | 90 + docshell/test/mochitest/test_bug1729662.html | 76 + docshell/test/mochitest/test_bug1740516.html | 79 + docshell/test/mochitest/test_bug1741132.html | 79 + docshell/test/mochitest/test_bug1742865.html | 137 + docshell/test/mochitest/test_bug1743353.html | 57 + docshell/test/mochitest/test_bug1747033.html | 97 + docshell/test/mochitest/test_bug1773192.html | 61 + docshell/test/mochitest/test_bug385434.html | 211 + docshell/test/mochitest/test_bug387979.html | 52 + docshell/test/mochitest/test_bug402210.html | 50 + docshell/test/mochitest/test_bug404548.html | 39 + docshell/test/mochitest/test_bug413310.html | 106 + docshell/test/mochitest/test_bug475636.html | 52 + docshell/test/mochitest/test_bug509055.html | 115 + docshell/test/mochitest/test_bug511449.html | 56 + docshell/test/mochitest/test_bug529119-1.html | 110 + docshell/test/mochitest/test_bug529119-2.html | 116 + docshell/test/mochitest/test_bug530396.html | 56 + docshell/test/mochitest/test_bug540462.html | 44 + docshell/test/mochitest/test_bug551225.html | 32 + docshell/test/mochitest/test_bug570341.html | 142 + docshell/test/mochitest/test_bug580069.html | 58 + docshell/test/mochitest/test_bug590573.html | 198 + docshell/test/mochitest/test_bug598895.html | 52 + docshell/test/mochitest/test_bug634834.html | 52 + docshell/test/mochitest/test_bug637644.html | 52 + docshell/test/mochitest/test_bug640387_1.html | 107 + docshell/test/mochitest/test_bug640387_2.html | 89 + docshell/test/mochitest/test_bug653741.html | 49 + docshell/test/mochitest/test_bug660404.html | 76 + docshell/test/mochitest/test_bug662170.html | 51 + docshell/test/mochitest/test_bug668513.html | 28 + docshell/test/mochitest/test_bug669671.html | 145 + docshell/test/mochitest/test_bug675587.html | 33 + docshell/test/mochitest/test_bug680257.html | 76 + docshell/test/mochitest/test_bug691547.html | 59 + docshell/test/mochitest/test_bug694612.html | 34 + docshell/test/mochitest/test_bug703855.html | 79 + docshell/test/mochitest/test_bug728939.html | 37 + docshell/test/mochitest/test_bug797909.html | 66 + .../test_close_onpagehide_by_history_back.html | 24 + .../test_close_onpagehide_by_window_close.html | 20 + .../test/mochitest/test_compressed_multipart.html | 41 + .../mochitest/test_content_javascript_loads.html | 163 + docshell/test/mochitest/test_double_submit.html | 98 + .../test_forceinheritprincipal_overrule_owner.html | 57 + docshell/test/mochitest/test_form_restoration.html | 77 + .../test/mochitest/test_framedhistoryframes.html | 32 + .../mochitest/test_iframe_srcdoc_to_remote.html | 44 + .../mochitest/test_javascript_sandboxed_popup.html | 27 + .../test/mochitest/test_load_during_reload.html | 49 + .../mochitest/test_navigate_after_pagehide.html | 34 + .../test_pushState_after_document_open.html | 39 + docshell/test/mochitest/test_redirect_history.html | 58 + .../test_triggeringprincipal_location_seturi.html | 105 + .../test/mochitest/test_windowedhistoryframes.html | 32 + docshell/test/mochitest/url1_historyframe.html | 1 + docshell/test/mochitest/url2_historyframe.html | 1 + docshell/test/moz.build | 137 + docshell/test/navigation/NavigationUtils.js | 203 + docshell/test/navigation/blank.html | 1 + docshell/test/navigation/bluebox_bug430723.html | 6 + docshell/test/navigation/browser.ini | 23 + docshell/test/navigation/browser_bug1757458.js | 46 + docshell/test/navigation/browser_bug343515.js | 276 + .../navigation/browser_ghistorymaxsize_is_0.js | 82 + .../navigation/browser_test-content-chromeflags.js | 54 + .../navigation/browser_test_bfcache_eviction.js | 102 + .../navigation/browser_test_shentry_wireframe.js | 128 + ...r_test_simultaneous_normal_and_history_loads.js | 57 + docshell/test/navigation/bug343515_pg1.html | 5 + docshell/test/navigation/bug343515_pg2.html | 7 + docshell/test/navigation/bug343515_pg3.html | 7 + docshell/test/navigation/bug343515_pg3_1.html | 6 + docshell/test/navigation/bug343515_pg3_1_1.html | 1 + docshell/test/navigation/bug343515_pg3_2.html | 1 + .../test/navigation/cache_control_max_age_3600.sjs | 20 + .../navigation/file_beforeunload_and_bfcache.html | 31 + docshell/test/navigation/file_blockBFCache.html | 33 + docshell/test/navigation/file_bug1300461.html | 61 + docshell/test/navigation/file_bug1300461_back.html | 37 + .../test/navigation/file_bug1300461_redirect.html | 10 + .../file_bug1300461_redirect.html^headers^ | 2 + docshell/test/navigation/file_bug1326251.html | 212 + .../navigation/file_bug1326251_evict_cache.html | 17 + docshell/test/navigation/file_bug1364364-1.html | 33 + docshell/test/navigation/file_bug1364364-2.html | 14 + .../test/navigation/file_bug1375833-frame1.html | 8 + .../test/navigation/file_bug1375833-frame2.html | 8 + docshell/test/navigation/file_bug1375833.html | 22 + docshell/test/navigation/file_bug1379762-1.html | 35 + docshell/test/navigation/file_bug1536471.html | 8 + docshell/test/navigation/file_bug1583110.html | 26 + docshell/test/navigation/file_bug1609475.html | 51 + docshell/test/navigation/file_bug1706090.html | 40 + docshell/test/navigation/file_bug1745638.html | 15 + docshell/test/navigation/file_bug1750973.html | 45 + docshell/test/navigation/file_bug1758664.html | 32 + .../navigation/file_bug386782_contenteditable.html | 1 + .../test/navigation/file_bug386782_designmode.html | 1 + docshell/test/navigation/file_bug462076_1.html | 55 + docshell/test/navigation/file_bug462076_2.html | 52 + docshell/test/navigation/file_bug462076_3.html | 52 + docshell/test/navigation/file_bug508537_1.html | 33 + docshell/test/navigation/file_bug534178.html | 30 + .../file_contentpolicy_block_window.html | 5 + .../test/navigation/file_docshell_gotoindex.html | 42 + .../test/navigation/file_document_write_1.html | 18 + .../test/navigation/file_evict_from_bfcache.html | 29 + .../file_fragment_handling_during_load.html | 27 + .../file_fragment_handling_during_load_frame1.html | 6 + .../file_fragment_handling_during_load_frame2.sjs | 20 + ...file_load_history_entry_page_with_one_link.html | 7 + ...ile_load_history_entry_page_with_two_links.html | 9 + docshell/test/navigation/file_meta_refresh.html | 40 + docshell/test/navigation/file_navigation_type.html | 25 + docshell/test/navigation/file_nested_frames.html | 27 + .../navigation/file_nested_frames_innerframe.html | 1 + docshell/test/navigation/file_nested_srcdoc.html | 3 + ...le_new_shentry_during_history_navigation_1.html | 5 + ...entry_during_history_navigation_1.html^headers^ | 1 + ...le_new_shentry_during_history_navigation_2.html | 10 + ...entry_during_history_navigation_2.html^headers^ | 1 + ...le_new_shentry_during_history_navigation_3.html | 22 + ...entry_during_history_navigation_3.html^headers^ | 1 + ...le_new_shentry_during_history_navigation_4.html | 16 + .../navigation/file_online_offline_bfcache.html | 41 + docshell/test/navigation/file_reload.html | 23 + .../test/navigation/file_reload_large_postdata.sjs | 46 + .../navigation/file_reload_nonbfcached_srcdoc.sjs | 27 + docshell/test/navigation/file_same_url.html | 24 + ...le_scrollRestoration_bfcache_and_nobfcache.html | 30 + ...ollRestoration_bfcache_and_nobfcache_part2.html | 35 + ...ation_bfcache_and_nobfcache_part2.html^headers^ | 1 + .../file_scrollRestoration_navigate.html | 17 + .../file_scrollRestoration_part1_nobfcache.html | 63 + ...scrollRestoration_part1_nobfcache.html^headers^ | 1 + .../file_scrollRestoration_part2_bfcache.html | 57 + .../file_scrollRestoration_part3_nobfcache.html | 157 + ...scrollRestoration_part3_nobfcache.html^headers^ | 1 + .../file_session_history_on_redirect.html | 16 + .../file_session_history_on_redirect.html^headers^ | 1 + .../file_session_history_on_redirect_2.html | 16 + ...ile_session_history_on_redirect_2.html^headers^ | 1 + .../file_sessionhistory_iframe_removal.html | 37 + .../navigation/file_shiftReload_and_pushState.html | 28 + .../navigation/file_ship_beforeunload_fired.html | 37 + .../test/navigation/file_static_and_dynamic_1.html | 31 + docshell/test/navigation/file_tell_opener.html | 8 + .../file_triggeringprincipal_frame_1.html | 27 + .../file_triggeringprincipal_frame_2.html | 8 + ...rincipal_iframe_iframe_window_open_frame_a.html | 6 + ...ipal_iframe_iframe_window_open_frame_a_nav.html | 6 + ...rincipal_iframe_iframe_window_open_frame_b.html | 15 + ...ngprincipal_parent_iframe_window_open_base.html | 6 + ...ingprincipal_parent_iframe_window_open_nav.html | 6 + .../file_triggeringprincipal_subframe.html | 15 + .../file_triggeringprincipal_subframe_nav.html | 21 + ...iggeringprincipal_subframe_same_origin_nav.html | 20 + .../file_triggeringprincipal_window_open.html | 6 + docshell/test/navigation/frame0.html | 3 + docshell/test/navigation/frame1.html | 3 + docshell/test/navigation/frame2.html | 3 + docshell/test/navigation/frame3.html | 3 + docshell/test/navigation/frame_1_out_of_6.html | 6 + docshell/test/navigation/frame_2_out_of_6.html | 6 + docshell/test/navigation/frame_3_out_of_6.html | 6 + docshell/test/navigation/frame_4_out_of_6.html | 6 + docshell/test/navigation/frame_5_out_of_6.html | 6 + docshell/test/navigation/frame_6_out_of_6.html | 6 + .../test/navigation/frame_load_as_example_com.html | 6 + .../test/navigation/frame_load_as_example_org.html | 6 + docshell/test/navigation/frame_load_as_host1.html | 6 + docshell/test/navigation/frame_load_as_host2.html | 6 + docshell/test/navigation/frame_load_as_host3.html | 6 + docshell/test/navigation/frame_recursive.html | 6 + docshell/test/navigation/goback.html | 5 + docshell/test/navigation/iframe.html | 9 + docshell/test/navigation/iframe_slow_onload.html | 5 + .../test/navigation/iframe_slow_onload_inner.html | 19 + docshell/test/navigation/iframe_static.html | 8 + docshell/test/navigation/mochitest.ini | 255 + docshell/test/navigation/navigate.html | 38 + .../navigation/navigation_target_popup_url.html | 1 + .../test/navigation/navigation_target_url.html | 1 + .../test/navigation/object_recursive_load.html | 6 + docshell/test/navigation/open.html | 10 + docshell/test/navigation/parent.html | 14 + docshell/test/navigation/redbox_bug430723.html | 6 + docshell/test/navigation/redirect_handlers.sjs | 29 + docshell/test/navigation/redirect_to_blank.sjs | 6 + docshell/test/navigation/slow.sjs | 16 + .../navigation/test_aboutblank_change_process.html | 46 + .../navigation/test_beforeunload_and_bfcache.html | 97 + docshell/test/navigation/test_blockBFCache.html | 294 + docshell/test/navigation/test_bug1300461.html | 70 + docshell/test/navigation/test_bug1326251.html | 47 + docshell/test/navigation/test_bug1364364.html | 65 + docshell/test/navigation/test_bug1375833.html | 131 + docshell/test/navigation/test_bug1379762.html | 67 + docshell/test/navigation/test_bug13871.html | 85 + docshell/test/navigation/test_bug145971.html | 29 + docshell/test/navigation/test_bug1536471.html | 75 + docshell/test/navigation/test_bug1583110.html | 36 + docshell/test/navigation/test_bug1609475.html | 35 + docshell/test/navigation/test_bug1699721.html | 110 + docshell/test/navigation/test_bug1706090.html | 49 + docshell/test/navigation/test_bug1745638.html | 40 + docshell/test/navigation/test_bug1747019.html | 48 + docshell/test/navigation/test_bug1750973.html | 20 + docshell/test/navigation/test_bug1758664.html | 21 + docshell/test/navigation/test_bug270414.html | 103 + docshell/test/navigation/test_bug278916.html | 37 + docshell/test/navigation/test_bug279495.html | 44 + docshell/test/navigation/test_bug344861.html | 35 + docshell/test/navigation/test_bug386782.html | 122 + docshell/test/navigation/test_bug430624.html | 57 + docshell/test/navigation/test_bug430723.html | 124 + docshell/test/navigation/test_child.html | 47 + .../test_contentpolicy_block_window.html | 98 + .../test/navigation/test_docshell_gotoindex.html | 29 + .../test_dynamic_frame_forward_back.html | 35 + .../test/navigation/test_evict_from_bfcache.html | 63 + .../test_fragment_handling_during_load.html | 35 + docshell/test/navigation/test_grandchild.html | 47 + .../test/navigation/test_load_history_entry.html | 196 + docshell/test/navigation/test_meta_refresh.html | 42 + docshell/test/navigation/test_navigation_type.html | 47 + docshell/test/navigation/test_nested_frames.html | 35 + ...test_new_shentry_during_history_navigation.html | 90 + docshell/test/navigation/test_not-opener.html | 56 + .../navigation/test_online_offline_bfcache.html | 101 + .../navigation/test_open_javascript_noopener.html | 44 + docshell/test/navigation/test_opener.html | 56 + .../navigation/test_performance_navigation.html | 41 + .../navigation/test_popup-navigates-children.html | 69 + .../test_rate_limit_location_change.html | 100 + .../test/navigation/test_recursive_frames.html | 167 + docshell/test/navigation/test_reload.html | 42 + .../navigation/test_reload_large_postdata.html | 61 + .../navigation/test_reload_nonbfcached_srcdoc.html | 40 + docshell/test/navigation/test_reserved.html | 92 + docshell/test/navigation/test_same_url.html | 56 + .../test/navigation/test_scrollRestoration.html | 214 + .../test_session_history_entry_cleanup.html | 35 + .../test_session_history_on_redirect.html | 92 + docshell/test/navigation/test_sessionhistory.html | 48 + .../test_sessionhistory_document_write.html | 34 + .../test_sessionhistory_iframe_removal.html | 33 + .../navigation/test_shiftReload_and_pushState.html | 35 + .../navigation/test_ship_beforeunload_fired.html | 63 + .../navigation/test_ship_beforeunload_fired_2.html | 65 + .../navigation/test_ship_beforeunload_fired_3.html | 65 + .../navigation/test_sibling-matching-parent.html | 46 + .../test/navigation/test_sibling-off-domain.html | 46 + docshell/test/navigation/test_state_size.html | 32 + .../test/navigation/test_static_and_dynamic.html | 36 + .../test_triggeringprincipal_frame_nav.html | 74 + ..._triggeringprincipal_frame_same_origin_nav.html | 63 + ...ggeringprincipal_iframe_iframe_window_open.html | 87 + ...ggeringprincipal_parent_iframe_window_open.html | 70 + .../test_triggeringprincipal_window_open.html | 79 + docshell/test/unit/AllowJavascriptChild.sys.mjs | 41 + docshell/test/unit/AllowJavascriptParent.sys.mjs | 28 + docshell/test/unit/data/engine.xml | 10 + docshell/test/unit/data/enginePost.xml | 10 + docshell/test/unit/data/enginePrivate.xml | 10 + docshell/test/unit/head_docshell.js | 106 + docshell/test/unit/test_URIFixup.js | 123 + docshell/test/unit/test_URIFixup_check_host.js | 183 + .../test_URIFixup_external_protocol_fallback.js | 106 + docshell/test/unit/test_URIFixup_forced.js | 159 + docshell/test/unit/test_URIFixup_info.js | 1075 ++ docshell/test/unit/test_URIFixup_search.js | 143 + docshell/test/unit/test_allowJavascript.js | 291 + .../unit/test_browsing_context_structured_clone.js | 70 + docshell/test/unit/test_bug442584.js | 35 + docshell/test/unit/test_pb_notification.js | 18 + docshell/test/unit/test_privacy_transition.js | 21 + .../unit/test_subframe_stop_after_parent_error.js | 146 + docshell/test/unit/xpcshell.ini | 35 + docshell/test/unit_ipc/test_pb_notification_ipc.js | 15 + docshell/test/unit_ipc/xpcshell.ini | 7 + 820 files changed, 83052 insertions(+) create mode 100644 docshell/base/BaseHistory.cpp create mode 100644 docshell/base/BaseHistory.h create mode 100644 docshell/base/BrowsingContext.cpp create mode 100644 docshell/base/BrowsingContext.h create mode 100644 docshell/base/BrowsingContextGroup.cpp create mode 100644 docshell/base/BrowsingContextGroup.h create mode 100644 docshell/base/BrowsingContextWebProgress.cpp create mode 100644 docshell/base/BrowsingContextWebProgress.h create mode 100644 docshell/base/CanonicalBrowsingContext.cpp create mode 100644 docshell/base/CanonicalBrowsingContext.h create mode 100644 docshell/base/ChildProcessChannelListener.cpp create mode 100644 docshell/base/ChildProcessChannelListener.h create mode 100644 docshell/base/IHistory.h create mode 100644 docshell/base/LoadContext.cpp create mode 100644 docshell/base/LoadContext.h create mode 100644 docshell/base/SerializedLoadContext.cpp create mode 100644 docshell/base/SerializedLoadContext.h create mode 100644 docshell/base/SyncedContext.h create mode 100644 docshell/base/SyncedContextInlines.h create mode 100644 docshell/base/URIFixup.sys.mjs create mode 100644 docshell/base/WindowContext.cpp create mode 100644 docshell/base/WindowContext.h create mode 100644 docshell/base/crashtests/1257730-1.html create mode 100644 docshell/base/crashtests/1331295.html create mode 100644 docshell/base/crashtests/1341657.html create mode 100644 docshell/base/crashtests/1584467.html create mode 100644 docshell/base/crashtests/1614211-1.html create mode 100644 docshell/base/crashtests/1617315-1.html create mode 100644 docshell/base/crashtests/1667491.html create mode 100644 docshell/base/crashtests/1667491_1.html create mode 100644 docshell/base/crashtests/1672873.html create mode 100644 docshell/base/crashtests/1690169-1.html create mode 100644 docshell/base/crashtests/1753136.html create mode 100644 docshell/base/crashtests/1804803.html create mode 100644 docshell/base/crashtests/1804803.sjs create mode 100644 docshell/base/crashtests/369126-1.html create mode 100644 docshell/base/crashtests/40929-1-inner.html create mode 100644 docshell/base/crashtests/40929-1.html create mode 100644 docshell/base/crashtests/430124-1.html create mode 100644 docshell/base/crashtests/430628-1.html create mode 100644 docshell/base/crashtests/432114-1.html create mode 100644 docshell/base/crashtests/432114-2.html create mode 100644 docshell/base/crashtests/436900-1-inner.html create mode 100644 docshell/base/crashtests/436900-1.html create mode 100644 docshell/base/crashtests/436900-2-inner.html create mode 100644 docshell/base/crashtests/436900-2.html create mode 100644 docshell/base/crashtests/443655.html create mode 100644 docshell/base/crashtests/500328-1.html create mode 100644 docshell/base/crashtests/514779-1.xhtml create mode 100644 docshell/base/crashtests/614499-1.html create mode 100644 docshell/base/crashtests/678872-1.html create mode 100644 docshell/base/crashtests/914521.html create mode 100644 docshell/base/crashtests/crashtests.list create mode 100644 docshell/base/crashtests/file_432114-2.xhtml create mode 100644 docshell/base/moz.build create mode 100644 docshell/base/nsAboutRedirector.cpp create mode 100644 docshell/base/nsAboutRedirector.h create mode 100644 docshell/base/nsCTooltipTextProvider.h create mode 100644 docshell/base/nsDSURIContentListener.cpp create mode 100644 docshell/base/nsDSURIContentListener.h create mode 100644 docshell/base/nsDocShell.cpp create mode 100644 docshell/base/nsDocShell.h create mode 100644 docshell/base/nsDocShellEditorData.cpp create mode 100644 docshell/base/nsDocShellEditorData.h create mode 100644 docshell/base/nsDocShellEnumerator.cpp create mode 100644 docshell/base/nsDocShellEnumerator.h create mode 100644 docshell/base/nsDocShellLoadState.cpp create mode 100644 docshell/base/nsDocShellLoadState.h create mode 100644 docshell/base/nsDocShellLoadTypes.h create mode 100644 docshell/base/nsDocShellTelemetryUtils.cpp create mode 100644 docshell/base/nsDocShellTelemetryUtils.h create mode 100644 docshell/base/nsDocShellTreeOwner.cpp create mode 100644 docshell/base/nsDocShellTreeOwner.h create mode 100644 docshell/base/nsIContentViewer.idl create mode 100644 docshell/base/nsIContentViewerEdit.idl create mode 100644 docshell/base/nsIDocShell.idl create mode 100644 docshell/base/nsIDocShellTreeItem.idl create mode 100644 docshell/base/nsIDocShellTreeOwner.idl create mode 100644 docshell/base/nsIDocumentLoaderFactory.idl create mode 100644 docshell/base/nsILoadContext.idl create mode 100644 docshell/base/nsILoadURIDelegate.idl create mode 100644 docshell/base/nsIPrivacyTransitionObserver.idl create mode 100644 docshell/base/nsIReflowObserver.idl create mode 100644 docshell/base/nsIRefreshURI.idl create mode 100644 docshell/base/nsIScrollObserver.h create mode 100644 docshell/base/nsITooltipListener.idl create mode 100644 docshell/base/nsITooltipTextProvider.idl create mode 100644 docshell/base/nsIURIFixup.idl create mode 100644 docshell/base/nsIWebNavigation.idl create mode 100644 docshell/base/nsIWebNavigationInfo.idl create mode 100644 docshell/base/nsIWebPageDescriptor.idl create mode 100644 docshell/base/nsPingListener.cpp create mode 100644 docshell/base/nsPingListener.h create mode 100644 docshell/base/nsRefreshTimer.cpp create mode 100644 docshell/base/nsRefreshTimer.h create mode 100644 docshell/base/nsWebNavigationInfo.cpp create mode 100644 docshell/base/nsWebNavigationInfo.h create mode 100644 docshell/base/timeline/AbstractTimelineMarker.cpp create mode 100644 docshell/base/timeline/AbstractTimelineMarker.h create mode 100644 docshell/base/timeline/AutoGlobalTimelineMarker.cpp create mode 100644 docshell/base/timeline/AutoGlobalTimelineMarker.h create mode 100644 docshell/base/timeline/AutoRestyleTimelineMarker.cpp create mode 100644 docshell/base/timeline/AutoRestyleTimelineMarker.h create mode 100644 docshell/base/timeline/AutoTimelineMarker.cpp create mode 100644 docshell/base/timeline/AutoTimelineMarker.h create mode 100644 docshell/base/timeline/CompositeTimelineMarker.h create mode 100644 docshell/base/timeline/ConsoleTimelineMarker.h create mode 100644 docshell/base/timeline/DocLoadingTimelineMarker.h create mode 100644 docshell/base/timeline/EventTimelineMarker.h create mode 100644 docshell/base/timeline/JavascriptTimelineMarker.h create mode 100644 docshell/base/timeline/LayerTimelineMarker.h create mode 100644 docshell/base/timeline/MarkersStorage.h create mode 100644 docshell/base/timeline/MessagePortTimelineMarker.h create mode 100644 docshell/base/timeline/ObservedDocShell.cpp create mode 100644 docshell/base/timeline/ObservedDocShell.h create mode 100644 docshell/base/timeline/RestyleTimelineMarker.h create mode 100644 docshell/base/timeline/TimelineConsumers.cpp create mode 100644 docshell/base/timeline/TimelineConsumers.h create mode 100644 docshell/base/timeline/TimelineMarker.cpp create mode 100644 docshell/base/timeline/TimelineMarker.h create mode 100644 docshell/base/timeline/TimelineMarkerEnums.h create mode 100644 docshell/base/timeline/TimestampTimelineMarker.h create mode 100644 docshell/base/timeline/WorkerTimelineMarker.h create mode 100644 docshell/base/timeline/moz.build create mode 100644 docshell/base/timeline/readme.md create mode 100644 docshell/build/components.conf create mode 100644 docshell/build/moz.build create mode 100644 docshell/build/nsDocShellCID.h create mode 100644 docshell/build/nsDocShellModule.cpp create mode 100644 docshell/build/nsDocShellModule.h create mode 100644 docshell/moz.build create mode 100644 docshell/shistory/ChildSHistory.cpp create mode 100644 docshell/shistory/ChildSHistory.h create mode 100644 docshell/shistory/SessionHistoryEntry.cpp create mode 100644 docshell/shistory/SessionHistoryEntry.h create mode 100644 docshell/shistory/moz.build create mode 100644 docshell/shistory/nsIBFCacheEntry.idl create mode 100644 docshell/shistory/nsISHEntry.idl create mode 100644 docshell/shistory/nsISHistory.idl create mode 100644 docshell/shistory/nsISHistoryListener.idl create mode 100644 docshell/shistory/nsSHEntry.cpp create mode 100644 docshell/shistory/nsSHEntry.h create mode 100644 docshell/shistory/nsSHEntryShared.cpp create mode 100644 docshell/shistory/nsSHEntryShared.h create mode 100644 docshell/shistory/nsSHistory.cpp create mode 100644 docshell/shistory/nsSHistory.h create mode 100644 docshell/test/browser/Bug1622420Child.sys.mjs create mode 100644 docshell/test/browser/Bug422543Child.sys.mjs create mode 100644 docshell/test/browser/browser.ini create mode 100644 docshell/test/browser/browser_alternate_fixup_middle_click_link.js create mode 100644 docshell/test/browser/browser_backforward_restore_scroll.js create mode 100644 docshell/test/browser/browser_backforward_userinteraction.js create mode 100644 docshell/test/browser/browser_backforward_userinteraction_about.js create mode 100644 docshell/test/browser/browser_backforward_userinteraction_systemprincipal.js create mode 100644 docshell/test/browser/browser_badCertDomainFixup.js create mode 100644 docshell/test/browser/browser_bfcache_copycommand.js create mode 100644 docshell/test/browser/browser_browsingContext-01.js create mode 100644 docshell/test/browser/browser_browsingContext-02.js create mode 100644 docshell/test/browser/browser_browsingContext-embedder.js create mode 100644 docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js create mode 100644 docshell/test/browser/browser_browsingContext-getWindowByName.js create mode 100644 docshell/test/browser/browser_browsingContext-webProgress.js create mode 100644 docshell/test/browser/browser_browsing_context_attached.js create mode 100644 docshell/test/browser/browser_browsing_context_discarded.js create mode 100644 docshell/test/browser/browser_bug1206879.js create mode 100644 docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js create mode 100644 docshell/test/browser/browser_bug1328501.js create mode 100644 docshell/test/browser/browser_bug1347823.js create mode 100644 docshell/test/browser/browser_bug134911.js create mode 100644 docshell/test/browser/browser_bug1415918_beforeunload_options.js create mode 100644 docshell/test/browser/browser_bug1543077-3.js create mode 100644 docshell/test/browser/browser_bug1594938.js create mode 100644 docshell/test/browser/browser_bug1622420.js create mode 100644 docshell/test/browser/browser_bug1648464-1.js create mode 100644 docshell/test/browser/browser_bug1673702.js create mode 100644 docshell/test/browser/browser_bug1674464.js create mode 100644 docshell/test/browser/browser_bug1688368-1.js create mode 100644 docshell/test/browser/browser_bug1691153.js create mode 100644 docshell/test/browser/browser_bug1705872.js create mode 100644 docshell/test/browser/browser_bug1716290-1.js create mode 100644 docshell/test/browser/browser_bug1716290-2.js create mode 100644 docshell/test/browser/browser_bug1716290-3.js create mode 100644 docshell/test/browser/browser_bug1716290-4.js create mode 100644 docshell/test/browser/browser_bug1719178.js create mode 100644 docshell/test/browser/browser_bug1736248-1.js create mode 100644 docshell/test/browser/browser_bug1757005.js create mode 100644 docshell/test/browser/browser_bug1769189.js create mode 100644 docshell/test/browser/browser_bug1798780.js create mode 100644 docshell/test/browser/browser_bug234628-1.js create mode 100644 docshell/test/browser/browser_bug234628-10.js create mode 100644 docshell/test/browser/browser_bug234628-11.js create mode 100644 docshell/test/browser/browser_bug234628-2.js create mode 100644 docshell/test/browser/browser_bug234628-3.js create mode 100644 docshell/test/browser/browser_bug234628-4.js create mode 100644 docshell/test/browser/browser_bug234628-5.js create mode 100644 docshell/test/browser/browser_bug234628-6.js create mode 100644 docshell/test/browser/browser_bug234628-8.js create mode 100644 docshell/test/browser/browser_bug234628-9.js create mode 100644 docshell/test/browser/browser_bug349769.js create mode 100644 docshell/test/browser/browser_bug388121-1.js create mode 100644 docshell/test/browser/browser_bug388121-2.js create mode 100644 docshell/test/browser/browser_bug420605.js create mode 100644 docshell/test/browser/browser_bug422543.js create mode 100644 docshell/test/browser/browser_bug441169.js create mode 100644 docshell/test/browser/browser_bug503832.js create mode 100644 docshell/test/browser/browser_bug554155.js create mode 100644 docshell/test/browser/browser_bug655270.js create mode 100644 docshell/test/browser/browser_bug655273.js create mode 100644 docshell/test/browser/browser_bug670318.js create mode 100644 docshell/test/browser/browser_bug673087-1.js create mode 100644 docshell/test/browser/browser_bug673087-2.js create mode 100644 docshell/test/browser/browser_bug673467.js create mode 100644 docshell/test/browser/browser_bug852909.js create mode 100644 docshell/test/browser/browser_bug92473.js create mode 100644 docshell/test/browser/browser_click_link_within_view_source.js create mode 100644 docshell/test/browser/browser_cross_process_csp_inheritance.js create mode 100644 docshell/test/browser/browser_csp_sandbox_no_script_js_uri.js create mode 100644 docshell/test/browser/browser_csp_uir.js create mode 100644 docshell/test/browser/browser_dataURI_unique_opaque_origin.js create mode 100644 docshell/test/browser/browser_data_load_inherit_csp.js create mode 100644 docshell/test/browser/browser_fall_back_to_https.js create mode 100644 docshell/test/browser/browser_frameloader_swap_with_bfcache.js create mode 100644 docshell/test/browser/browser_history_triggeringprincipal_viewsource.js create mode 100644 docshell/test/browser/browser_isInitialDocument.js create mode 100644 docshell/test/browser/browser_loadURI_postdata.js create mode 100644 docshell/test/browser/browser_multiple_pushState.js create mode 100644 docshell/test/browser/browser_onbeforeunload_frame.js create mode 100644 docshell/test/browser/browser_onbeforeunload_navigation.js create mode 100644 docshell/test/browser/browser_onbeforeunload_parent.js create mode 100644 docshell/test/browser/browser_onunload_stop.js create mode 100644 docshell/test/browser/browser_overlink.js create mode 100644 docshell/test/browser/browser_platform_emulation.js create mode 100644 docshell/test/browser/browser_search_notification.js create mode 100644 docshell/test/browser/browser_tab_replace_while_loading.js create mode 100644 docshell/test/browser/browser_tab_touch_events.js create mode 100644 docshell/test/browser/browser_targetTopLevelLinkClicksToBlank.js create mode 100644 docshell/test/browser/browser_timelineMarkers-01.js create mode 100644 docshell/test/browser/browser_timelineMarkers-02.js create mode 100644 docshell/test/browser/browser_timelineMarkers-frame-02.js create mode 100644 docshell/test/browser/browser_title_in_session_history.js create mode 100644 docshell/test/browser/browser_ua_emulation.js create mode 100644 docshell/test/browser/browser_uriFixupAlternateRedirects.js create mode 100644 docshell/test/browser/browser_uriFixupIntegration.js create mode 100644 docshell/test/browser/browser_viewsource_chrome_to_content.js create mode 100644 docshell/test/browser/browser_viewsource_multipart.js create mode 100644 docshell/test/browser/dummy_iframe_page.html create mode 100644 docshell/test/browser/dummy_page.html create mode 100644 docshell/test/browser/favicon_bug655270.ico create mode 100644 docshell/test/browser/file_backforward_restore_scroll.html create mode 100644 docshell/test/browser/file_backforward_restore_scroll.html^headers^ create mode 100644 docshell/test/browser/file_basic_multipart.sjs create mode 100644 docshell/test/browser/file_bug1046022.html create mode 100644 docshell/test/browser/file_bug1206879.html create mode 100644 docshell/test/browser/file_bug1328501.html create mode 100644 docshell/test/browser/file_bug1328501_frame.html create mode 100644 docshell/test/browser/file_bug1328501_framescript.js create mode 100644 docshell/test/browser/file_bug1543077-3-child.html create mode 100644 docshell/test/browser/file_bug1543077-3.html create mode 100644 docshell/test/browser/file_bug1622420.html create mode 100644 docshell/test/browser/file_bug1648464-1-child.html create mode 100644 docshell/test/browser/file_bug1648464-1.html create mode 100644 docshell/test/browser/file_bug1673702.json create mode 100644 docshell/test/browser/file_bug1673702.json^headers^ create mode 100644 docshell/test/browser/file_bug1688368-1.sjs create mode 100644 docshell/test/browser/file_bug1691153.html create mode 100644 docshell/test/browser/file_bug1716290-1.sjs create mode 100644 docshell/test/browser/file_bug1716290-2.sjs create mode 100644 docshell/test/browser/file_bug1716290-3.sjs create mode 100644 docshell/test/browser/file_bug1716290-4.sjs create mode 100644 docshell/test/browser/file_bug1736248-1.html create mode 100644 docshell/test/browser/file_bug234628-1-child.html create mode 100644 docshell/test/browser/file_bug234628-1.html create mode 100644 docshell/test/browser/file_bug234628-10-child.xhtml create mode 100644 docshell/test/browser/file_bug234628-10.html create mode 100644 docshell/test/browser/file_bug234628-11-child.xhtml create mode 100644 docshell/test/browser/file_bug234628-11-child.xhtml^headers^ create mode 100644 docshell/test/browser/file_bug234628-11.html create mode 100644 docshell/test/browser/file_bug234628-2-child.html create mode 100644 docshell/test/browser/file_bug234628-2.html create mode 100644 docshell/test/browser/file_bug234628-3-child.html create mode 100644 docshell/test/browser/file_bug234628-3.html create mode 100644 docshell/test/browser/file_bug234628-4-child.html create mode 100644 docshell/test/browser/file_bug234628-4.html create mode 100644 docshell/test/browser/file_bug234628-5-child.html create mode 100644 docshell/test/browser/file_bug234628-5.html create mode 100644 docshell/test/browser/file_bug234628-6-child.html create mode 100644 docshell/test/browser/file_bug234628-6-child.html^headers^ create mode 100644 docshell/test/browser/file_bug234628-6.html create mode 100644 docshell/test/browser/file_bug234628-8-child.html create mode 100644 docshell/test/browser/file_bug234628-8.html create mode 100644 docshell/test/browser/file_bug234628-9-child.html create mode 100644 docshell/test/browser/file_bug234628-9.html create mode 100644 docshell/test/browser/file_bug420605.html create mode 100644 docshell/test/browser/file_bug503832.html create mode 100644 docshell/test/browser/file_bug655270.html create mode 100644 docshell/test/browser/file_bug670318.html create mode 100644 docshell/test/browser/file_bug673087-1-child.html create mode 100644 docshell/test/browser/file_bug673087-1.html create mode 100644 docshell/test/browser/file_bug673087-1.html^headers^ create mode 100644 docshell/test/browser/file_bug673087-2.html create mode 100644 docshell/test/browser/file_bug852909.pdf create mode 100644 docshell/test/browser/file_bug852909.png create mode 100644 docshell/test/browser/file_click_link_within_view_source.html create mode 100644 docshell/test/browser/file_cross_process_csp_inheritance.html create mode 100644 docshell/test/browser/file_csp_sandbox_no_script_js_uri.html create mode 100644 docshell/test/browser/file_csp_sandbox_no_script_js_uri.html^headers^ create mode 100644 docshell/test/browser/file_csp_uir.html create mode 100644 docshell/test/browser/file_csp_uir_dummy.html create mode 100644 docshell/test/browser/file_data_load_inherit_csp.html create mode 100644 docshell/test/browser/file_multiple_pushState.html create mode 100644 docshell/test/browser/file_onbeforeunload_0.html create mode 100644 docshell/test/browser/file_onbeforeunload_1.html create mode 100644 docshell/test/browser/file_onbeforeunload_2.html create mode 100644 docshell/test/browser/file_onbeforeunload_3.html create mode 100644 docshell/test/browser/file_open_about_blank.html create mode 100644 docshell/test/browser/file_slow_load.sjs create mode 100644 docshell/test/browser/frame-head.js create mode 100644 docshell/test/browser/head.js create mode 100644 docshell/test/browser/head_browser_onbeforeunload.js create mode 100644 docshell/test/browser/onload_message.html create mode 100644 docshell/test/browser/onpageshow_message.html create mode 100644 docshell/test/browser/overlink_test.html create mode 100644 docshell/test/browser/print_postdata.sjs create mode 100644 docshell/test/browser/redirect_to_example.sjs create mode 100644 docshell/test/browser/test-form_sjis.html create mode 100644 docshell/test/chrome/112564_nocache.html create mode 100644 docshell/test/chrome/112564_nocache.html^headers^ create mode 100644 docshell/test/chrome/215405_nocache.html create mode 100644 docshell/test/chrome/215405_nocache.html^headers^ create mode 100644 docshell/test/chrome/215405_nostore.html create mode 100644 docshell/test/chrome/215405_nostore.html^headers^ create mode 100644 docshell/test/chrome/582176_dummy.html create mode 100644 docshell/test/chrome/582176_xml.xml create mode 100644 docshell/test/chrome/582176_xslt.xsl create mode 100644 docshell/test/chrome/662200a.html create mode 100644 docshell/test/chrome/662200b.html create mode 100644 docshell/test/chrome/662200c.html create mode 100644 docshell/test/chrome/89419.html create mode 100644 docshell/test/chrome/92598_nostore.html create mode 100644 docshell/test/chrome/92598_nostore.html^headers^ create mode 100644 docshell/test/chrome/DocShellHelpers.sys.mjs create mode 100644 docshell/test/chrome/allowContentRetargeting.sjs create mode 100644 docshell/test/chrome/blue.png create mode 100644 docshell/test/chrome/bug112564_window.xhtml create mode 100644 docshell/test/chrome/bug113934_window.xhtml create mode 100644 docshell/test/chrome/bug215405_window.xhtml create mode 100644 docshell/test/chrome/bug293235.html create mode 100644 docshell/test/chrome/bug293235_p2.html create mode 100644 docshell/test/chrome/bug293235_window.xhtml create mode 100644 docshell/test/chrome/bug294258_testcase.html create mode 100644 docshell/test/chrome/bug294258_window.xhtml create mode 100644 docshell/test/chrome/bug298622_window.xhtml create mode 100644 docshell/test/chrome/bug301397_1.html create mode 100644 docshell/test/chrome/bug301397_2.html create mode 100644 docshell/test/chrome/bug301397_3.html create mode 100644 docshell/test/chrome/bug301397_4.html create mode 100644 docshell/test/chrome/bug301397_window.xhtml create mode 100644 docshell/test/chrome/bug303267.html create mode 100644 docshell/test/chrome/bug303267_window.xhtml create mode 100644 docshell/test/chrome/bug311007_window.xhtml create mode 100644 docshell/test/chrome/bug321671_window.xhtml create mode 100644 docshell/test/chrome/bug360511_case1.html create mode 100644 docshell/test/chrome/bug360511_case2.html create mode 100644 docshell/test/chrome/bug360511_window.xhtml create mode 100644 docshell/test/chrome/bug364461_window.xhtml create mode 100644 docshell/test/chrome/bug396519_window.xhtml create mode 100644 docshell/test/chrome/bug396649_window.xhtml create mode 100644 docshell/test/chrome/bug449778_window.xhtml create mode 100644 docshell/test/chrome/bug449780_window.xhtml create mode 100644 docshell/test/chrome/bug454235-subframe.xhtml create mode 100644 docshell/test/chrome/bug582176_window.xhtml create mode 100644 docshell/test/chrome/bug608669.xhtml create mode 100644 docshell/test/chrome/bug662200_window.xhtml create mode 100644 docshell/test/chrome/bug690056_window.xhtml create mode 100644 docshell/test/chrome/bug846906.html create mode 100644 docshell/test/chrome/bug89419.sjs create mode 100644 docshell/test/chrome/bug89419_window.xhtml create mode 100644 docshell/test/chrome/bug909218.html create mode 100644 docshell/test/chrome/bug909218.js create mode 100644 docshell/test/chrome/bug92598_window.xhtml create mode 100644 docshell/test/chrome/chrome.ini create mode 100644 docshell/test/chrome/docshell_helpers.js create mode 100644 docshell/test/chrome/file_viewsource_forbidden_in_iframe.html create mode 100644 docshell/test/chrome/gen_template.pl create mode 100644 docshell/test/chrome/generic.html create mode 100644 docshell/test/chrome/mozFrameType_window.xhtml create mode 100644 docshell/test/chrome/red.png create mode 100644 docshell/test/chrome/test.template.txt create mode 100644 docshell/test/chrome/test_allowContentRetargeting.html create mode 100644 docshell/test/chrome/test_bug112564.xhtml create mode 100644 docshell/test/chrome/test_bug113934.xhtml create mode 100644 docshell/test/chrome/test_bug215405.xhtml create mode 100644 docshell/test/chrome/test_bug293235.xhtml create mode 100644 docshell/test/chrome/test_bug294258.xhtml create mode 100644 docshell/test/chrome/test_bug298622.xhtml create mode 100644 docshell/test/chrome/test_bug301397.xhtml create mode 100644 docshell/test/chrome/test_bug303267.xhtml create mode 100644 docshell/test/chrome/test_bug311007.xhtml create mode 100644 docshell/test/chrome/test_bug321671.xhtml create mode 100644 docshell/test/chrome/test_bug360511.xhtml create mode 100644 docshell/test/chrome/test_bug364461.xhtml create mode 100644 docshell/test/chrome/test_bug396519.xhtml create mode 100644 docshell/test/chrome/test_bug396649.xhtml create mode 100644 docshell/test/chrome/test_bug428288.html create mode 100644 docshell/test/chrome/test_bug449778.xhtml create mode 100644 docshell/test/chrome/test_bug449780.xhtml create mode 100644 docshell/test/chrome/test_bug453650.xhtml create mode 100644 docshell/test/chrome/test_bug454235.xhtml create mode 100644 docshell/test/chrome/test_bug456980.xhtml create mode 100644 docshell/test/chrome/test_bug565388.xhtml create mode 100644 docshell/test/chrome/test_bug582176.xhtml create mode 100644 docshell/test/chrome/test_bug608669.xhtml create mode 100644 docshell/test/chrome/test_bug662200.xhtml create mode 100644 docshell/test/chrome/test_bug690056.xhtml create mode 100644 docshell/test/chrome/test_bug789773.xhtml create mode 100644 docshell/test/chrome/test_bug846906.xhtml create mode 100644 docshell/test/chrome/test_bug89419.xhtml create mode 100644 docshell/test/chrome/test_bug909218.html create mode 100644 docshell/test/chrome/test_bug92598.xhtml create mode 100644 docshell/test/chrome/test_docRedirect.sjs create mode 100644 docshell/test/chrome/test_docRedirect.xhtml create mode 100644 docshell/test/chrome/test_mozFrameType.xhtml create mode 100644 docshell/test/chrome/test_open_and_immediately_close_opener.html create mode 100644 docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml create mode 100644 docshell/test/chrome/window.template.txt create mode 100644 docshell/test/iframesandbox/file_child_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/file_marquee_event_handlers.html create mode 100644 docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/file_parent_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/file_sibling_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/file_top_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html create mode 100644 docshell/test/iframesandbox/file_top_navigation_by_user_activation.html create mode 100644 docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html create mode 100644 docshell/test/iframesandbox/mochitest.ini create mode 100644 docshell/test/iframesandbox/test_child_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/test_marquee_event_handlers.html create mode 100644 docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/test_parent_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/test_sibling_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/test_top_navigation_by_location.html create mode 100644 docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html create mode 100644 docshell/test/iframesandbox/test_top_navigation_by_user_activation.html create mode 100644 docshell/test/mochitest/bug1422334_redirect.html create mode 100644 docshell/test/mochitest/bug1422334_redirect.html^headers^ create mode 100644 docshell/test/mochitest/bug404548-subframe.html create mode 100644 docshell/test/mochitest/bug404548-subframe_window.html create mode 100644 docshell/test/mochitest/bug413310-post.sjs create mode 100644 docshell/test/mochitest/bug413310-subframe.html create mode 100644 docshell/test/mochitest/bug529119-window.html create mode 100644 docshell/test/mochitest/bug530396-noref.sjs create mode 100644 docshell/test/mochitest/bug530396-subframe.html create mode 100644 docshell/test/mochitest/bug570341_recordevents.html create mode 100644 docshell/test/mochitest/bug668513_redirect.html create mode 100644 docshell/test/mochitest/bug668513_redirect.html^headers^ create mode 100644 docshell/test/mochitest/bug691547_frame.html create mode 100644 docshell/test/mochitest/clicker.html create mode 100644 docshell/test/mochitest/double_submit.sjs create mode 100644 docshell/test/mochitest/dummy_page.html create mode 100644 docshell/test/mochitest/file_anchor_scroll_after_document_open.html create mode 100644 docshell/test/mochitest/file_bfcache_plus_hash_1.html create mode 100644 docshell/test/mochitest/file_bfcache_plus_hash_2.html create mode 100644 docshell/test/mochitest/file_bug1121701_1.html create mode 100644 docshell/test/mochitest/file_bug1121701_2.html create mode 100644 docshell/test/mochitest/file_bug1151421.html create mode 100644 docshell/test/mochitest/file_bug1186774.html create mode 100644 docshell/test/mochitest/file_bug1450164.html create mode 100644 docshell/test/mochitest/file_bug1729662.html create mode 100644 docshell/test/mochitest/file_bug1740516_1.html create mode 100644 docshell/test/mochitest/file_bug1740516_1_inner.html create mode 100644 docshell/test/mochitest/file_bug1740516_2.html create mode 100644 docshell/test/mochitest/file_bug1741132.html create mode 100644 docshell/test/mochitest/file_bug1742865.sjs create mode 100644 docshell/test/mochitest/file_bug1742865_outer.sjs create mode 100644 docshell/test/mochitest/file_bug1743353.html create mode 100644 docshell/test/mochitest/file_bug1747033.sjs create mode 100644 docshell/test/mochitest/file_bug1773192_1.html create mode 100644 docshell/test/mochitest/file_bug1773192_2.html create mode 100644 docshell/test/mochitest/file_bug1773192_3.sjs create mode 100644 docshell/test/mochitest/file_bug385434_1.html create mode 100644 docshell/test/mochitest/file_bug385434_2.html create mode 100644 docshell/test/mochitest/file_bug385434_3.html create mode 100644 docshell/test/mochitest/file_bug475636.sjs create mode 100644 docshell/test/mochitest/file_bug509055.html create mode 100644 docshell/test/mochitest/file_bug511449.html create mode 100644 docshell/test/mochitest/file_bug540462.html create mode 100644 docshell/test/mochitest/file_bug580069_1.html create mode 100644 docshell/test/mochitest/file_bug580069_2.sjs create mode 100644 docshell/test/mochitest/file_bug590573_1.html create mode 100644 docshell/test/mochitest/file_bug590573_2.html create mode 100644 docshell/test/mochitest/file_bug598895_1.html create mode 100644 docshell/test/mochitest/file_bug598895_2.html create mode 100644 docshell/test/mochitest/file_bug634834.html create mode 100644 docshell/test/mochitest/file_bug637644_1.html create mode 100644 docshell/test/mochitest/file_bug637644_2.html create mode 100644 docshell/test/mochitest/file_bug640387.html create mode 100644 docshell/test/mochitest/file_bug653741.html create mode 100644 docshell/test/mochitest/file_bug660404 create mode 100644 docshell/test/mochitest/file_bug660404-1.html create mode 100644 docshell/test/mochitest/file_bug660404^headers^ create mode 100644 docshell/test/mochitest/file_bug662170.html create mode 100644 docshell/test/mochitest/file_bug668513.html create mode 100644 docshell/test/mochitest/file_bug669671.sjs create mode 100644 docshell/test/mochitest/file_bug675587.html create mode 100644 docshell/test/mochitest/file_bug680257.html create mode 100644 docshell/test/mochitest/file_bug703855.html create mode 100644 docshell/test/mochitest/file_bug728939.html create mode 100644 docshell/test/mochitest/file_close_onpagehide1.html create mode 100644 docshell/test/mochitest/file_close_onpagehide2.html create mode 100644 docshell/test/mochitest/file_compressed_multipart create mode 100644 docshell/test/mochitest/file_compressed_multipart^headers^ create mode 100644 docshell/test/mochitest/file_content_javascript_loads_frame.html create mode 100644 docshell/test/mochitest/file_content_javascript_loads_root.html create mode 100644 docshell/test/mochitest/file_form_restoration_no_store.html create mode 100644 docshell/test/mochitest/file_form_restoration_no_store.html^headers^ create mode 100644 docshell/test/mochitest/file_framedhistoryframes.html create mode 100644 docshell/test/mochitest/file_load_during_reload.html create mode 100644 docshell/test/mochitest/file_pushState_after_document_open.html create mode 100644 docshell/test/mochitest/file_redirect_history.html create mode 100644 docshell/test/mochitest/form_submit.sjs create mode 100644 docshell/test/mochitest/form_submit_redirect.sjs create mode 100644 docshell/test/mochitest/historyframes.html create mode 100644 docshell/test/mochitest/mochitest.ini create mode 100644 docshell/test/mochitest/ping.html create mode 100644 docshell/test/mochitest/start_historyframe.html create mode 100644 docshell/test/mochitest/test_anchor_scroll_after_document_open.html create mode 100644 docshell/test/mochitest/test_bfcache_plus_hash.html create mode 100644 docshell/test/mochitest/test_bug1045096.html create mode 100644 docshell/test/mochitest/test_bug1121701.html create mode 100644 docshell/test/mochitest/test_bug1151421.html create mode 100644 docshell/test/mochitest/test_bug1186774.html create mode 100644 docshell/test/mochitest/test_bug1422334.html create mode 100644 docshell/test/mochitest/test_bug1450164.html create mode 100644 docshell/test/mochitest/test_bug1507702.html create mode 100644 docshell/test/mochitest/test_bug1645781.html create mode 100644 docshell/test/mochitest/test_bug1729662.html create mode 100644 docshell/test/mochitest/test_bug1740516.html create mode 100644 docshell/test/mochitest/test_bug1741132.html create mode 100644 docshell/test/mochitest/test_bug1742865.html create mode 100644 docshell/test/mochitest/test_bug1743353.html create mode 100644 docshell/test/mochitest/test_bug1747033.html create mode 100644 docshell/test/mochitest/test_bug1773192.html create mode 100644 docshell/test/mochitest/test_bug385434.html create mode 100644 docshell/test/mochitest/test_bug387979.html create mode 100644 docshell/test/mochitest/test_bug402210.html create mode 100644 docshell/test/mochitest/test_bug404548.html create mode 100644 docshell/test/mochitest/test_bug413310.html create mode 100644 docshell/test/mochitest/test_bug475636.html create mode 100644 docshell/test/mochitest/test_bug509055.html create mode 100644 docshell/test/mochitest/test_bug511449.html create mode 100644 docshell/test/mochitest/test_bug529119-1.html create mode 100644 docshell/test/mochitest/test_bug529119-2.html create mode 100644 docshell/test/mochitest/test_bug530396.html create mode 100644 docshell/test/mochitest/test_bug540462.html create mode 100644 docshell/test/mochitest/test_bug551225.html create mode 100644 docshell/test/mochitest/test_bug570341.html create mode 100644 docshell/test/mochitest/test_bug580069.html create mode 100644 docshell/test/mochitest/test_bug590573.html create mode 100644 docshell/test/mochitest/test_bug598895.html create mode 100644 docshell/test/mochitest/test_bug634834.html create mode 100644 docshell/test/mochitest/test_bug637644.html create mode 100644 docshell/test/mochitest/test_bug640387_1.html create mode 100644 docshell/test/mochitest/test_bug640387_2.html create mode 100644 docshell/test/mochitest/test_bug653741.html create mode 100644 docshell/test/mochitest/test_bug660404.html create mode 100644 docshell/test/mochitest/test_bug662170.html create mode 100644 docshell/test/mochitest/test_bug668513.html create mode 100644 docshell/test/mochitest/test_bug669671.html create mode 100644 docshell/test/mochitest/test_bug675587.html create mode 100644 docshell/test/mochitest/test_bug680257.html create mode 100644 docshell/test/mochitest/test_bug691547.html create mode 100644 docshell/test/mochitest/test_bug694612.html create mode 100644 docshell/test/mochitest/test_bug703855.html create mode 100644 docshell/test/mochitest/test_bug728939.html create mode 100644 docshell/test/mochitest/test_bug797909.html create mode 100644 docshell/test/mochitest/test_close_onpagehide_by_history_back.html create mode 100644 docshell/test/mochitest/test_close_onpagehide_by_window_close.html create mode 100644 docshell/test/mochitest/test_compressed_multipart.html create mode 100644 docshell/test/mochitest/test_content_javascript_loads.html create mode 100644 docshell/test/mochitest/test_double_submit.html create mode 100644 docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html create mode 100644 docshell/test/mochitest/test_form_restoration.html create mode 100644 docshell/test/mochitest/test_framedhistoryframes.html create mode 100644 docshell/test/mochitest/test_iframe_srcdoc_to_remote.html create mode 100644 docshell/test/mochitest/test_javascript_sandboxed_popup.html create mode 100644 docshell/test/mochitest/test_load_during_reload.html create mode 100644 docshell/test/mochitest/test_navigate_after_pagehide.html create mode 100644 docshell/test/mochitest/test_pushState_after_document_open.html create mode 100644 docshell/test/mochitest/test_redirect_history.html create mode 100644 docshell/test/mochitest/test_triggeringprincipal_location_seturi.html create mode 100644 docshell/test/mochitest/test_windowedhistoryframes.html create mode 100644 docshell/test/mochitest/url1_historyframe.html create mode 100644 docshell/test/mochitest/url2_historyframe.html create mode 100644 docshell/test/moz.build create mode 100644 docshell/test/navigation/NavigationUtils.js create mode 100644 docshell/test/navigation/blank.html create mode 100644 docshell/test/navigation/bluebox_bug430723.html create mode 100644 docshell/test/navigation/browser.ini create mode 100644 docshell/test/navigation/browser_bug1757458.js create mode 100644 docshell/test/navigation/browser_bug343515.js create mode 100644 docshell/test/navigation/browser_ghistorymaxsize_is_0.js create mode 100644 docshell/test/navigation/browser_test-content-chromeflags.js create mode 100644 docshell/test/navigation/browser_test_bfcache_eviction.js create mode 100644 docshell/test/navigation/browser_test_shentry_wireframe.js create mode 100644 docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js create mode 100644 docshell/test/navigation/bug343515_pg1.html create mode 100644 docshell/test/navigation/bug343515_pg2.html create mode 100644 docshell/test/navigation/bug343515_pg3.html create mode 100644 docshell/test/navigation/bug343515_pg3_1.html create mode 100644 docshell/test/navigation/bug343515_pg3_1_1.html create mode 100644 docshell/test/navigation/bug343515_pg3_2.html create mode 100644 docshell/test/navigation/cache_control_max_age_3600.sjs create mode 100644 docshell/test/navigation/file_beforeunload_and_bfcache.html create mode 100644 docshell/test/navigation/file_blockBFCache.html create mode 100644 docshell/test/navigation/file_bug1300461.html create mode 100644 docshell/test/navigation/file_bug1300461_back.html create mode 100644 docshell/test/navigation/file_bug1300461_redirect.html create mode 100644 docshell/test/navigation/file_bug1300461_redirect.html^headers^ create mode 100644 docshell/test/navigation/file_bug1326251.html create mode 100644 docshell/test/navigation/file_bug1326251_evict_cache.html create mode 100644 docshell/test/navigation/file_bug1364364-1.html create mode 100644 docshell/test/navigation/file_bug1364364-2.html create mode 100644 docshell/test/navigation/file_bug1375833-frame1.html create mode 100644 docshell/test/navigation/file_bug1375833-frame2.html create mode 100644 docshell/test/navigation/file_bug1375833.html create mode 100644 docshell/test/navigation/file_bug1379762-1.html create mode 100644 docshell/test/navigation/file_bug1536471.html create mode 100644 docshell/test/navigation/file_bug1583110.html create mode 100644 docshell/test/navigation/file_bug1609475.html create mode 100644 docshell/test/navigation/file_bug1706090.html create mode 100644 docshell/test/navigation/file_bug1745638.html create mode 100644 docshell/test/navigation/file_bug1750973.html create mode 100644 docshell/test/navigation/file_bug1758664.html create mode 100644 docshell/test/navigation/file_bug386782_contenteditable.html create mode 100644 docshell/test/navigation/file_bug386782_designmode.html create mode 100644 docshell/test/navigation/file_bug462076_1.html create mode 100644 docshell/test/navigation/file_bug462076_2.html create mode 100644 docshell/test/navigation/file_bug462076_3.html create mode 100644 docshell/test/navigation/file_bug508537_1.html create mode 100644 docshell/test/navigation/file_bug534178.html create mode 100644 docshell/test/navigation/file_contentpolicy_block_window.html create mode 100644 docshell/test/navigation/file_docshell_gotoindex.html create mode 100644 docshell/test/navigation/file_document_write_1.html create mode 100644 docshell/test/navigation/file_evict_from_bfcache.html create mode 100644 docshell/test/navigation/file_fragment_handling_during_load.html create mode 100644 docshell/test/navigation/file_fragment_handling_during_load_frame1.html create mode 100644 docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs create mode 100644 docshell/test/navigation/file_load_history_entry_page_with_one_link.html create mode 100644 docshell/test/navigation/file_load_history_entry_page_with_two_links.html create mode 100644 docshell/test/navigation/file_meta_refresh.html create mode 100644 docshell/test/navigation/file_navigation_type.html create mode 100644 docshell/test/navigation/file_nested_frames.html create mode 100644 docshell/test/navigation/file_nested_frames_innerframe.html create mode 100644 docshell/test/navigation/file_nested_srcdoc.html create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_1.html create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^ create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_2.html create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^ create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_3.html create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^ create mode 100644 docshell/test/navigation/file_new_shentry_during_history_navigation_4.html create mode 100644 docshell/test/navigation/file_online_offline_bfcache.html create mode 100644 docshell/test/navigation/file_reload.html create mode 100644 docshell/test/navigation/file_reload_large_postdata.sjs create mode 100644 docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs create mode 100644 docshell/test/navigation/file_same_url.html create mode 100644 docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html create mode 100644 docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html create mode 100644 docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^ create mode 100644 docshell/test/navigation/file_scrollRestoration_navigate.html create mode 100644 docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html create mode 100644 docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^ create mode 100644 docshell/test/navigation/file_scrollRestoration_part2_bfcache.html create mode 100644 docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html create mode 100644 docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^ create mode 100644 docshell/test/navigation/file_session_history_on_redirect.html create mode 100644 docshell/test/navigation/file_session_history_on_redirect.html^headers^ create mode 100644 docshell/test/navigation/file_session_history_on_redirect_2.html create mode 100644 docshell/test/navigation/file_session_history_on_redirect_2.html^headers^ create mode 100644 docshell/test/navigation/file_sessionhistory_iframe_removal.html create mode 100644 docshell/test/navigation/file_shiftReload_and_pushState.html create mode 100644 docshell/test/navigation/file_ship_beforeunload_fired.html create mode 100644 docshell/test/navigation/file_static_and_dynamic_1.html create mode 100644 docshell/test/navigation/file_tell_opener.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_frame_1.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_frame_2.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_subframe.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_subframe_nav.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html create mode 100644 docshell/test/navigation/file_triggeringprincipal_window_open.html create mode 100644 docshell/test/navigation/frame0.html create mode 100644 docshell/test/navigation/frame1.html create mode 100644 docshell/test/navigation/frame2.html create mode 100644 docshell/test/navigation/frame3.html create mode 100644 docshell/test/navigation/frame_1_out_of_6.html create mode 100644 docshell/test/navigation/frame_2_out_of_6.html create mode 100644 docshell/test/navigation/frame_3_out_of_6.html create mode 100644 docshell/test/navigation/frame_4_out_of_6.html create mode 100644 docshell/test/navigation/frame_5_out_of_6.html create mode 100644 docshell/test/navigation/frame_6_out_of_6.html create mode 100644 docshell/test/navigation/frame_load_as_example_com.html create mode 100644 docshell/test/navigation/frame_load_as_example_org.html create mode 100644 docshell/test/navigation/frame_load_as_host1.html create mode 100644 docshell/test/navigation/frame_load_as_host2.html create mode 100644 docshell/test/navigation/frame_load_as_host3.html create mode 100644 docshell/test/navigation/frame_recursive.html create mode 100644 docshell/test/navigation/goback.html create mode 100644 docshell/test/navigation/iframe.html create mode 100644 docshell/test/navigation/iframe_slow_onload.html create mode 100644 docshell/test/navigation/iframe_slow_onload_inner.html create mode 100644 docshell/test/navigation/iframe_static.html create mode 100644 docshell/test/navigation/mochitest.ini create mode 100644 docshell/test/navigation/navigate.html create mode 100644 docshell/test/navigation/navigation_target_popup_url.html create mode 100644 docshell/test/navigation/navigation_target_url.html create mode 100644 docshell/test/navigation/object_recursive_load.html create mode 100644 docshell/test/navigation/open.html create mode 100644 docshell/test/navigation/parent.html create mode 100644 docshell/test/navigation/redbox_bug430723.html create mode 100644 docshell/test/navigation/redirect_handlers.sjs create mode 100644 docshell/test/navigation/redirect_to_blank.sjs create mode 100644 docshell/test/navigation/slow.sjs create mode 100644 docshell/test/navigation/test_aboutblank_change_process.html create mode 100644 docshell/test/navigation/test_beforeunload_and_bfcache.html create mode 100644 docshell/test/navigation/test_blockBFCache.html create mode 100644 docshell/test/navigation/test_bug1300461.html create mode 100644 docshell/test/navigation/test_bug1326251.html create mode 100644 docshell/test/navigation/test_bug1364364.html create mode 100644 docshell/test/navigation/test_bug1375833.html create mode 100644 docshell/test/navigation/test_bug1379762.html create mode 100644 docshell/test/navigation/test_bug13871.html create mode 100644 docshell/test/navigation/test_bug145971.html create mode 100644 docshell/test/navigation/test_bug1536471.html create mode 100644 docshell/test/navigation/test_bug1583110.html create mode 100644 docshell/test/navigation/test_bug1609475.html create mode 100644 docshell/test/navigation/test_bug1699721.html create mode 100644 docshell/test/navigation/test_bug1706090.html create mode 100644 docshell/test/navigation/test_bug1745638.html create mode 100644 docshell/test/navigation/test_bug1747019.html create mode 100644 docshell/test/navigation/test_bug1750973.html create mode 100644 docshell/test/navigation/test_bug1758664.html create mode 100644 docshell/test/navigation/test_bug270414.html create mode 100644 docshell/test/navigation/test_bug278916.html create mode 100644 docshell/test/navigation/test_bug279495.html create mode 100644 docshell/test/navigation/test_bug344861.html create mode 100644 docshell/test/navigation/test_bug386782.html create mode 100644 docshell/test/navigation/test_bug430624.html create mode 100644 docshell/test/navigation/test_bug430723.html create mode 100644 docshell/test/navigation/test_child.html create mode 100644 docshell/test/navigation/test_contentpolicy_block_window.html create mode 100644 docshell/test/navigation/test_docshell_gotoindex.html create mode 100644 docshell/test/navigation/test_dynamic_frame_forward_back.html create mode 100644 docshell/test/navigation/test_evict_from_bfcache.html create mode 100644 docshell/test/navigation/test_fragment_handling_during_load.html create mode 100644 docshell/test/navigation/test_grandchild.html create mode 100644 docshell/test/navigation/test_load_history_entry.html create mode 100644 docshell/test/navigation/test_meta_refresh.html create mode 100644 docshell/test/navigation/test_navigation_type.html create mode 100644 docshell/test/navigation/test_nested_frames.html create mode 100644 docshell/test/navigation/test_new_shentry_during_history_navigation.html create mode 100644 docshell/test/navigation/test_not-opener.html create mode 100644 docshell/test/navigation/test_online_offline_bfcache.html create mode 100644 docshell/test/navigation/test_open_javascript_noopener.html create mode 100644 docshell/test/navigation/test_opener.html create mode 100644 docshell/test/navigation/test_performance_navigation.html create mode 100644 docshell/test/navigation/test_popup-navigates-children.html create mode 100644 docshell/test/navigation/test_rate_limit_location_change.html create mode 100644 docshell/test/navigation/test_recursive_frames.html create mode 100644 docshell/test/navigation/test_reload.html create mode 100644 docshell/test/navigation/test_reload_large_postdata.html create mode 100644 docshell/test/navigation/test_reload_nonbfcached_srcdoc.html create mode 100644 docshell/test/navigation/test_reserved.html create mode 100644 docshell/test/navigation/test_same_url.html create mode 100644 docshell/test/navigation/test_scrollRestoration.html create mode 100644 docshell/test/navigation/test_session_history_entry_cleanup.html create mode 100644 docshell/test/navigation/test_session_history_on_redirect.html create mode 100644 docshell/test/navigation/test_sessionhistory.html create mode 100644 docshell/test/navigation/test_sessionhistory_document_write.html create mode 100644 docshell/test/navigation/test_sessionhistory_iframe_removal.html create mode 100644 docshell/test/navigation/test_shiftReload_and_pushState.html create mode 100644 docshell/test/navigation/test_ship_beforeunload_fired.html create mode 100644 docshell/test/navigation/test_ship_beforeunload_fired_2.html create mode 100644 docshell/test/navigation/test_ship_beforeunload_fired_3.html create mode 100644 docshell/test/navigation/test_sibling-matching-parent.html create mode 100644 docshell/test/navigation/test_sibling-off-domain.html create mode 100644 docshell/test/navigation/test_state_size.html create mode 100644 docshell/test/navigation/test_static_and_dynamic.html create mode 100644 docshell/test/navigation/test_triggeringprincipal_frame_nav.html create mode 100644 docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html create mode 100644 docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html create mode 100644 docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html create mode 100644 docshell/test/navigation/test_triggeringprincipal_window_open.html create mode 100644 docshell/test/unit/AllowJavascriptChild.sys.mjs create mode 100644 docshell/test/unit/AllowJavascriptParent.sys.mjs create mode 100644 docshell/test/unit/data/engine.xml create mode 100644 docshell/test/unit/data/enginePost.xml create mode 100644 docshell/test/unit/data/enginePrivate.xml create mode 100644 docshell/test/unit/head_docshell.js create mode 100644 docshell/test/unit/test_URIFixup.js create mode 100644 docshell/test/unit/test_URIFixup_check_host.js create mode 100644 docshell/test/unit/test_URIFixup_external_protocol_fallback.js create mode 100644 docshell/test/unit/test_URIFixup_forced.js create mode 100644 docshell/test/unit/test_URIFixup_info.js create mode 100644 docshell/test/unit/test_URIFixup_search.js create mode 100644 docshell/test/unit/test_allowJavascript.js create mode 100644 docshell/test/unit/test_browsing_context_structured_clone.js create mode 100644 docshell/test/unit/test_bug442584.js create mode 100644 docshell/test/unit/test_pb_notification.js create mode 100644 docshell/test/unit/test_privacy_transition.js create mode 100644 docshell/test/unit/test_subframe_stop_after_parent_error.js create mode 100644 docshell/test/unit/xpcshell.ini create mode 100644 docshell/test/unit_ipc/test_pb_notification_ipc.js create mode 100644 docshell/test/unit_ipc/xpcshell.ini (limited to 'docshell') diff --git a/docshell/base/BaseHistory.cpp b/docshell/base/BaseHistory.cpp new file mode 100644 index 0000000000..a38fb71657 --- /dev/null +++ b/docshell/base/BaseHistory.cpp @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BaseHistory.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Link.h" +#include "mozilla/dom/Element.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_layout.h" + +namespace mozilla { + +using mozilla::dom::ContentParent; +using mozilla::dom::Link; + +BaseHistory::BaseHistory() : mTrackedURIs(kTrackedUrisInitialSize) {} + +BaseHistory::~BaseHistory() = default; + +static constexpr nsLiteralCString kDisallowedSchemes[] = { + "about"_ns, "blob"_ns, "data"_ns, "chrome"_ns, + "imap"_ns, "javascript"_ns, "mailbox"_ns, "moz-anno"_ns, + "news"_ns, "page-icon"_ns, "resource"_ns, "view-source"_ns, + "moz-extension"_ns, +}; + +bool BaseHistory::CanStore(nsIURI* aURI) { + nsAutoCString scheme; + if (NS_WARN_IF(NS_FAILED(aURI->GetScheme(scheme)))) { + return false; + } + + if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { + for (const nsLiteralCString& disallowed : kDisallowedSchemes) { + if (scheme.Equals(disallowed)) { + return false; + } + } + } + + nsAutoCString spec; + aURI->GetSpec(spec); + return spec.Length() <= StaticPrefs::browser_history_maxUrlLength(); +} + +void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI, + dom::ContentParent* aForProcess) { + mPendingQueries.WithEntryHandle(aURI, [&](auto&& entry) { + auto& set = entry.OrInsertWith([] { return ContentParentSet(); }); + if (aForProcess) { + set.Insert(aForProcess); + } + }); + if (mStartPendingVisitedQueriesScheduled) { + return; + } + mStartPendingVisitedQueriesScheduled = + NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NS_NewRunnableFunction( + "BaseHistory::StartPendingVisitedQueries", + [self = RefPtr(this)] { + self->mStartPendingVisitedQueriesScheduled = false; + auto queries = std::move(self->mPendingQueries); + self->StartPendingVisitedQueries(std::move(queries)); + MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty()); + }), + EventQueuePriority::Idle)); +} + +void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) { + mPendingQueries.Remove(aURI); + // TODO(bug 1591393): It could be worth to make this virtual and allow places + // to stop the existing database query? Needs some measurement. +} + +void BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI, "Must pass a non-null URI!"); + MOZ_ASSERT(aLink, "Must pass a non-null Link!"); + + if (!CanStore(aURI)) { + aLink->VisitedQueryFinished(/* visited = */ false); + return; + } + + // Obtain our array of observers for this URI. + auto* const links = + mTrackedURIs.WithEntryHandle(aURI, [&](auto&& entry) -> ObservingLinks* { + MOZ_DIAGNOSTIC_ASSERT(!entry || !entry->mLinks.IsEmpty(), + "An empty key was kept around in our hashtable!"); + if (!entry) { + ScheduleVisitedQuery(aURI, nullptr); + } + + return &entry.OrInsertWith([] { return ObservingLinks{}; }); + }); + + if (!links) { + return; + } + + // Sanity check that Links are not registered more than once for a given URI. + // This will not catch a case where it is registered for two different URIs. + MOZ_DIAGNOSTIC_ASSERT(!links->mLinks.Contains(aLink), + "Already tracking this Link object!"); + + links->mLinks.AppendElement(aLink); + + // If this link has already been queried and we should notify, do so now. + switch (links->mStatus) { + case VisitedStatus::Unknown: + break; + case VisitedStatus::Unvisited: + [[fallthrough]]; + case VisitedStatus::Visited: + aLink->VisitedQueryFinished(links->mStatus == VisitedStatus::Visited); + break; + } +} + +void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI, "Must pass a non-null URI!"); + MOZ_ASSERT(aLink, "Must pass a non-null Link object!"); + + // Get the array, and remove the item from it. + auto entry = mTrackedURIs.Lookup(aURI); + if (!entry) { + MOZ_ASSERT(!CanStore(aURI), + "Trying to unregister URI that wasn't registered, " + "and that could be visited!"); + return; + } + + ObserverArray& observers = entry->mLinks; + if (!observers.RemoveElement(aLink)) { + MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!"); + return; + } + + // If the array is now empty, we should remove it from the hashtable. + if (observers.IsEmpty()) { + entry.Remove(); + CancelVisitedQueryIfPossible(aURI); + } +} + +void BaseHistory::NotifyVisited( + nsIURI* aURI, VisitedStatus aStatus, + const ContentParentSet* aListOfProcessesToNotify) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aStatus != VisitedStatus::Unknown); + + NotifyVisitedInThisProcess(aURI, aStatus); + if (XRE_IsParentProcess()) { + NotifyVisitedFromParent(aURI, aStatus, aListOfProcessesToNotify); + } +} + +void BaseHistory::NotifyVisitedInThisProcess(nsIURI* aURI, + VisitedStatus aStatus) { + if (NS_WARN_IF(!aURI)) { + return; + } + + auto entry = mTrackedURIs.Lookup(aURI); + if (!entry) { + // If we have no observers for this URI, we have nothing to notify about. + return; + } + + ObservingLinks& links = entry.Data(); + links.mStatus = aStatus; + + // If we have a key, it should have at least one observer. + MOZ_ASSERT(!links.mLinks.IsEmpty()); + + // Dispatch an event to each document which has a Link observing this URL. + // These will fire asynchronously in the correct DocGroup. + + const bool visited = aStatus == VisitedStatus::Visited; + for (Link* link : links.mLinks.BackwardRange()) { + link->VisitedQueryFinished(visited); + } +} + +void BaseHistory::SendPendingVisitedResultsToChildProcesses() { + MOZ_ASSERT(!mPendingResults.IsEmpty()); + + mStartPendingResultsScheduled = false; + + auto results = std::move(mPendingResults); + MOZ_ASSERT(mPendingResults.IsEmpty()); + + nsTArray cplist; + nsTArray resultsForProcess; + ContentParent::GetAll(cplist); + for (ContentParent* cp : cplist) { + resultsForProcess.ClearAndRetainStorage(); + for (auto& result : results) { + if (result.mProcessesToNotify.IsEmpty() || + result.mProcessesToNotify.Contains(cp)) { + resultsForProcess.AppendElement(result.mResult); + } + } + if (!resultsForProcess.IsEmpty()) { + Unused << NS_WARN_IF(!cp->SendNotifyVisited(resultsForProcess)); + } + } +} + +void BaseHistory::NotifyVisitedFromParent( + nsIURI* aURI, VisitedStatus aStatus, + const ContentParentSet* aListOfProcessesToNotify) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (aListOfProcessesToNotify && aListOfProcessesToNotify->IsEmpty()) { + return; + } + + auto& result = *mPendingResults.AppendElement(); + result.mResult.visited() = aStatus == VisitedStatus::Visited; + result.mResult.uri() = aURI; + if (aListOfProcessesToNotify) { + for (auto* entry : *aListOfProcessesToNotify) { + result.mProcessesToNotify.Insert(entry); + } + } + + if (mStartPendingResultsScheduled) { + return; + } + + mStartPendingResultsScheduled = NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NewRunnableMethod( + "BaseHistory::SendPendingVisitedResultsToChildProcesses", this, + &BaseHistory::SendPendingVisitedResultsToChildProcesses), + EventQueuePriority::Idle)); +} + +} // namespace mozilla diff --git a/docshell/base/BaseHistory.h b/docshell/base/BaseHistory.h new file mode 100644 index 0000000000..f0fa36db99 --- /dev/null +++ b/docshell/base/BaseHistory.h @@ -0,0 +1,85 @@ +/* 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/. */ + +#ifndef mozilla_BaseHistory_h +#define mozilla_BaseHistory_h + +#include "IHistory.h" +#include "mozilla/dom/ContentParent.h" +#include "nsTHashSet.h" + +/* A base class for history implementations that implement link coloring. */ + +namespace mozilla { + +class BaseHistory : public IHistory { + public: + void RegisterVisitedCallback(nsIURI*, dom::Link*) final; + void ScheduleVisitedQuery(nsIURI*, dom::ContentParent*) final; + void UnregisterVisitedCallback(nsIURI*, dom::Link*) final; + void NotifyVisited(nsIURI*, VisitedStatus, + const ContentParentSet* = nullptr) final; + + // Some URIs like data-uris are never going to be stored in history, so we can + // avoid doing IPC roundtrips for them or what not. + static bool CanStore(nsIURI*); + + protected: + void NotifyVisitedInThisProcess(nsIURI*, VisitedStatus); + void NotifyVisitedFromParent(nsIURI*, VisitedStatus, const ContentParentSet*); + static constexpr const size_t kTrackedUrisInitialSize = 64; + + BaseHistory(); + ~BaseHistory(); + + using ObserverArray = nsTObserverArray; + struct ObservingLinks { + ObserverArray mLinks; + VisitedStatus mStatus = VisitedStatus::Unknown; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mLinks.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + }; + + using PendingVisitedQueries = nsTHashMap; + struct PendingVisitedResult { + dom::VisitedQueryResult mResult; + ContentParentSet mProcessesToNotify; + }; + using PendingVisitedResults = nsTArray; + + // Starts all the queries in the pending queries list, potentially at the same + // time. + virtual void StartPendingVisitedQueries(PendingVisitedQueries&&) = 0; + + private: + // Cancels a visited query, if it is at all possible, because we know we won't + // use the results anymore. + void CancelVisitedQueryIfPossible(nsIURI*); + + void SendPendingVisitedResultsToChildProcesses(); + + protected: + // A map from URI to links that depend on that URI, and whether that URI is + // known-to-be-visited-or-unvisited already. + nsTHashMap mTrackedURIs; + + private: + // The set of pending URIs that we haven't queried yet but need to. + PendingVisitedQueries mPendingQueries; + // The set of pending query results that we still haven't dispatched to child + // processes. + PendingVisitedResults mPendingResults; + // Whether we've successfully scheduled a runnable to call + // StartPendingVisitedQueries already. + bool mStartPendingVisitedQueriesScheduled = false; + // Whether we've successfully scheduled a runnable to call + // SendPendingVisitedResultsToChildProcesses already. + bool mStartPendingResultsScheduled = false; +}; + +} // namespace mozilla + +#endif diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp new file mode 100644 index 0000000000..e515c4e9aa --- /dev/null +++ b/docshell/base/BrowsingContext.cpp @@ -0,0 +1,3832 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContext.h" + +#include "ipc/IPCMessageUtils.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleParent.h" +# include "mozilla/a11y/Platform.h" +# include "mozilla/a11y/RemoteAccessibleBase.h" +# include "nsAccessibilityService.h" +# if defined(XP_WIN) +# include "mozilla/a11y/AccessibleWrap.h" +# include "mozilla/a11y/Compatibility.h" +# include "mozilla/a11y/nsWinUtils.h" +# endif +#endif +#include "mozilla/AppShutdown.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLEmbedElement.h" +#include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/Location.h" +#include "mozilla/dom/LocationBinding.h" +#include "mozilla/dom/MediaDevices.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SessionStoreChild.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/UserActivationIPCUtils.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/dom/SyncedContextInlines.h" +#include "mozilla/dom/XULFrameElement.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/net/RequestContextService.h" +#include "mozilla/Assertions.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Components.h" +#include "mozilla/HashTable.h" +#include "mozilla/Logging.h" +#include "mozilla/MediaFeatureChange.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_page_load.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/URLQueryStringStripper.h" +#include "mozilla/EventStateManager.h" +#include "nsIURIFixup.h" +#include "nsIXULRuntime.h" + +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsFocusManager.h" +#include "nsGlobalWindowOuter.h" +#include "PresShell.h" +#include "nsIObserverService.h" +#include "nsISHistory.h" +#include "nsContentUtils.h" +#include "nsQueryObject.h" +#include "nsSandboxFlags.h" +#include "nsScriptError.h" +#include "nsThreadUtils.h" +#include "xpcprivate.h" + +#include "AutoplayPolicy.h" +#include "GVAutoplayRequestStatusIPC.h" + +extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gTimeoutDeferralLog; + +#define AUTOPLAY_LOG(msg, ...) \ + MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace IPC { +// Allow serialization and deserialization of OrientationType over IPC +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::OrientationType, + mozilla::dom::OrientationType::Portrait_primary, + mozilla::dom::OrientationType::EndGuard_> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::PrefersColorSchemeOverride, + mozilla::dom::PrefersColorSchemeOverride::None, + mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::ExplicitActiveStatus, + mozilla::dom::ExplicitActiveStatus::None, + mozilla::dom::ExplicitActiveStatus::EndGuard_> {}; + +// Allow serialization and deserialization of TouchEventsOverride over IPC +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::TouchEventsOverride, + mozilla::dom::TouchEventsOverride::Disabled, + mozilla::dom::TouchEventsOverride::EndGuard_> {}; + +template <> +struct ParamTraits { + using paramType = mozilla::dom::EmbedderColorSchemes; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mUsed); + WriteParam(aWriter, aParam.mPreferred); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mUsed) && + ReadParam(aReader, &aResult->mPreferred); + } +}; + +} // namespace IPC + +namespace mozilla { +namespace dom { + +// Explicit specialization of the `Transaction` type. Required by the `extern +// template class` declaration in the header. +template class syncedcontext::Transaction; + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +static LazyLogModule gBrowsingContextLog("BrowsingContext"); +static LazyLogModule gBrowsingContextSyncLog("BrowsingContextSync"); + +typedef nsTHashMap BrowsingContextMap; + +// All BrowsingContexts indexed by Id +static StaticAutoPtr sBrowsingContexts; +// Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id +static StaticAutoPtr sCurrentTopByBrowserId; + +static void UnregisterBrowserId(BrowsingContext* aBrowsingContext) { + if (!aBrowsingContext->IsTopContent() || !sCurrentTopByBrowserId) { + return; + } + + // Avoids an extra lookup + auto browserIdEntry = + sCurrentTopByBrowserId->Lookup(aBrowsingContext->BrowserId()); + if (browserIdEntry && browserIdEntry.Data() == aBrowsingContext) { + browserIdEntry.Remove(); + } +} + +static void Register(BrowsingContext* aBrowsingContext) { + sBrowsingContexts->InsertOrUpdate(aBrowsingContext->Id(), aBrowsingContext); + if (aBrowsingContext->IsTopContent()) { + sCurrentTopByBrowserId->InsertOrUpdate(aBrowsingContext->BrowserId(), + aBrowsingContext); + } + + aBrowsingContext->Group()->Register(aBrowsingContext); +} + +// static +void BrowsingContext::UpdateCurrentTopByBrowserId( + BrowsingContext* aNewBrowsingContext) { + if (aNewBrowsingContext->IsTopContent()) { + sCurrentTopByBrowserId->InsertOrUpdate(aNewBrowsingContext->BrowserId(), + aNewBrowsingContext); + } +} + +BrowsingContext* BrowsingContext::GetParent() const { + return mParentWindow ? mParentWindow->GetBrowsingContext() : nullptr; +} + +bool BrowsingContext::IsInSubtreeOf(BrowsingContext* aContext) { + BrowsingContext* bc = this; + do { + if (bc == aContext) { + return true; + } + } while ((bc = bc->GetParent())); + return false; +} + +BrowsingContext* BrowsingContext::Top() { + BrowsingContext* bc = this; + while (bc->mParentWindow) { + bc = bc->GetParent(); + } + return bc; +} + +const BrowsingContext* BrowsingContext::Top() const { + const BrowsingContext* bc = this; + while (bc->mParentWindow) { + bc = bc->GetParent(); + } + return bc; +} + +int32_t BrowsingContext::IndexOf(BrowsingContext* aChild) { + int32_t index = -1; + for (BrowsingContext* child : Children()) { + ++index; + if (child == aChild) { + break; + } + } + return index; +} + +WindowContext* BrowsingContext::GetTopWindowContext() const { + if (mParentWindow) { + return mParentWindow->TopWindowContext(); + } + return mCurrentWindowContext; +} + +/* static */ +void BrowsingContext::Init() { + if (!sBrowsingContexts) { + sBrowsingContexts = new BrowsingContextMap(); + sCurrentTopByBrowserId = new BrowsingContextMap(); + ClearOnShutdown(&sBrowsingContexts); + ClearOnShutdown(&sCurrentTopByBrowserId); + } +} + +/* static */ +LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; } + +/* static */ +LogModule* BrowsingContext::GetSyncLog() { return gBrowsingContextSyncLog; } + +/* static */ +already_AddRefed BrowsingContext::Get(uint64_t aId) { + return do_AddRef(sBrowsingContexts->Get(aId)); +} + +/* static */ +already_AddRefed BrowsingContext::GetCurrentTopByBrowserId( + uint64_t aBrowserId) { + return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId)); +} + +/* static */ +already_AddRefed BrowsingContext::GetFromWindow( + WindowProxyHolder& aProxy) { + return do_AddRef(aProxy.get()); +} + +CanonicalBrowsingContext* BrowsingContext::Canonical() { + return CanonicalBrowsingContext::Cast(this); +} + +bool BrowsingContext::IsOwnedByProcess() const { + return mIsInProcess && mDocShell && + !nsDocShell::Cast(mDocShell)->WillChangeProcess(); +} + +bool BrowsingContext::SameOriginWithTop() { + MOZ_ASSERT(IsInProcess()); + // If the top BrowsingContext is not same-process to us, it is cross-origin + if (!Top()->IsInProcess()) { + return false; + } + + nsIDocShell* docShell = GetDocShell(); + if (!docShell) { + return false; + } + Document* doc = docShell->GetDocument(); + if (!doc) { + return false; + } + nsIPrincipal* principal = doc->NodePrincipal(); + + nsIDocShell* topDocShell = Top()->GetDocShell(); + if (!topDocShell) { + return false; + } + Document* topDoc = topDocShell->GetDocument(); + if (!topDoc) { + return false; + } + nsIPrincipal* topPrincipal = topDoc->NodePrincipal(); + + return principal->Equals(topPrincipal); +} + +/* static */ +already_AddRefed BrowsingContext::CreateDetached( + nsGlobalWindowInner* aParent, BrowsingContext* aOpener, + BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType, + bool aIsPopupRequested, bool aCreatedDynamically) { + if (aParent) { + MOZ_DIAGNOSTIC_ASSERT(aParent->GetWindowContext()); + MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->mType == aType); + MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->GetBrowserId() != 0); + } + + MOZ_DIAGNOSTIC_ASSERT(aType != Type::Chrome || XRE_IsParentProcess()); + + uint64_t id = nsContentUtils::GenerateBrowsingContextId(); + + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Creating 0x%08" PRIx64 " in %s", id, + XRE_IsParentProcess() ? "Parent" : "Child")); + + RefPtr parentBC = + aParent ? aParent->GetBrowsingContext() : nullptr; + RefPtr parentWC = + aParent ? aParent->GetWindowContext() : nullptr; + BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener; + + // Determine which BrowsingContextGroup this context should be created in. + RefPtr group = aSpecificGroup; + if (aType == Type::Chrome) { + MOZ_DIAGNOSTIC_ASSERT(!group); + group = BrowsingContextGroup::GetChromeGroup(); + } else if (!group) { + group = BrowsingContextGroup::Select(parentWC, aOpener); + } + + // Configure initial values for synced fields. + FieldValues fields; + fields.Get() = aName; + + if (aOpener) { + MOZ_DIAGNOSTIC_ASSERT(!aParent, + "new BC with both initial opener and parent"); + MOZ_DIAGNOSTIC_ASSERT(aOpener->Group() == group); + MOZ_DIAGNOSTIC_ASSERT(aOpener->mType == aType); + fields.Get() = aOpener->Id(); + fields.Get() = true; + } + + if (aParent) { + MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group); + MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType); + fields.Get() = aParent->WindowID(); + // Non-toplevel content documents are always embededed within content. + fields.Get() = + parentBC->mType == Type::Content; + + // XXX(farre): Can/Should we check aParent->IsLoading() here? (Bug + // 1608448) Check if the parent was itself loading already + auto readystate = aParent->GetDocument()->GetReadyStateEnum(); + fields.Get() = + parentBC->GetAncestorLoading() || + readystate == Document::ReadyState::READYSTATE_LOADING || + readystate == Document::ReadyState::READYSTATE_INTERACTIVE; + } + + fields.Get() = + parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId(); + + fields.Get() = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; + if (aOpener && aOpener->SameOriginWithTop()) { + // We inherit the opener policy if there is a creator and if the creator's + // origin is same origin with the creator's top-level origin. + // If it is cross origin we should not inherit the CrossOriginOpenerPolicy + fields.Get() = aOpener->Top()->GetOpenerPolicy(); + + // If we inherit a policy which is potentially cross-origin isolated, we + // must be in a potentially cross-origin isolated BCG. + bool isPotentiallyCrossOriginIsolated = + fields.Get() == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP; + MOZ_RELEASE_ASSERT(isPotentiallyCrossOriginIsolated == + group->IsPotentiallyCrossOriginIsolated()); + } else if (aOpener) { + // They are not same origin + auto topPolicy = aOpener->Top()->GetOpenerPolicy(); + MOZ_RELEASE_ASSERT(topPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE || + topPolicy == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS); + } else if (!aParent && group->IsPotentiallyCrossOriginIsolated()) { + // If we're creating a brand-new toplevel BC in a potentially cross-origin + // isolated group, it should start out with a strict opener policy. + fields.Get() = + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP; + } + + fields.Get() = nsID::GenerateUUID(); + fields.Get() = [&] { + if (parentBC) { + // Non-root browsing-contexts inherit their status from its parent. + return ExplicitActiveStatus::None; + } + if (aType == Type::Content) { + // Content gets managed by the chrome front-end / embedder element and + // starts as inactive. + return ExplicitActiveStatus::Inactive; + } + // Chrome starts as active. + return ExplicitActiveStatus::Active; + }(); + + fields.Get() = parentBC ? parentBC->FullZoom() : 1.0f; + fields.Get() = parentBC ? parentBC->TextZoom() : 1.0f; + + bool allowContentRetargeting = + inherit ? inherit->GetAllowContentRetargetingOnChildren() : true; + fields.Get() = allowContentRetargeting; + fields.Get() = allowContentRetargeting; + + // Assume top allows fullscreen for its children unless otherwise stated. + // Subframes start with it false unless otherwise noted in SetEmbedderElement. + fields.Get() = !aParent; + + fields.Get() = inherit ? inherit->GetAllowPlugins() : true; + + fields.Get() = + inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL; + + fields.Get() = mozilla::hal::ScreenOrientation::None; + + fields.Get() = + inherit ? inherit->GetUseGlobalHistory() : false; + + fields.Get() = true; + + fields.Get() = TouchEventsOverride::None; + + fields.Get() = + inherit ? inherit->GetAllowJavascript() : true; + + fields.Get() = aIsPopupRequested; + + if (!parentBC) { + fields.Get() = + StaticPrefs::media_block_autoplay_until_in_foreground(); + } + + RefPtr context; + if (XRE_IsParentProcess()) { + context = new CanonicalBrowsingContext(parentWC, group, id, + /* aOwnerProcessId */ 0, + /* aEmbedderProcessId */ 0, aType, + std::move(fields)); + } else { + context = + new BrowsingContext(parentWC, group, id, aType, std::move(fields)); + } + + context->mEmbeddedByThisProcess = XRE_IsParentProcess() || aParent; + context->mCreatedDynamically = aCreatedDynamically; + if (inherit) { + context->mPrivateBrowsingId = inherit->mPrivateBrowsingId; + context->mUseRemoteTabs = inherit->mUseRemoteTabs; + context->mUseRemoteSubframes = inherit->mUseRemoteSubframes; + context->mOriginAttributes = inherit->mOriginAttributes; + } + + nsCOMPtr rcsvc = + net::RequestContextService::GetOrCreate(); + if (rcsvc) { + nsCOMPtr requestContext; + nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext)); + if (NS_SUCCEEDED(rv) && requestContext) { + context->mRequestContextId = requestContext->GetID(); + } + } + + return context.forget(); +} + +already_AddRefed BrowsingContext::CreateIndependent( + Type aType) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), + "BCs created in the content process must be related to " + "some BrowserChild"); + RefPtr bc( + CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType, false)); + bc->mWindowless = bc->IsContent(); + bc->mEmbeddedByThisProcess = true; + bc->EnsureAttached(); + return bc.forget(); +} + +void BrowsingContext::EnsureAttached() { + if (!mEverAttached) { + Register(this); + + // Attach the browsing context to the tree. + Attach(/* aFromIPC */ false, /* aOriginProcess */ nullptr); + } +} + +/* static */ +mozilla::ipc::IPCResult BrowsingContext::CreateFromIPC( + BrowsingContext::IPCInitializer&& aInit, BrowsingContextGroup* aGroup, + ContentParent* aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess()); + MOZ_DIAGNOSTIC_ASSERT(aGroup); + + uint64_t originId = 0; + if (aOriginProcess) { + originId = aOriginProcess->ChildID(); + aGroup->EnsureHostProcess(aOriginProcess); + } + + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")", + aInit.mId, originId)); + + RefPtr parent = aInit.GetParent(); + + RefPtr context; + if (XRE_IsParentProcess()) { + // If the new BrowsingContext has a parent, it is a sub-frame embedded in + // whatever process sent the message. If it doesn't, and is not windowless, + // it is a new window or tab, and will be embedded in the parent process. + uint64_t embedderProcessId = (aInit.mWindowless || parent) ? originId : 0; + context = new CanonicalBrowsingContext(parent, aGroup, aInit.mId, originId, + embedderProcessId, Type::Content, + std::move(aInit.mFields)); + } else { + context = new BrowsingContext(parent, aGroup, aInit.mId, Type::Content, + std::move(aInit.mFields)); + } + + context->mWindowless = aInit.mWindowless; + context->mCreatedDynamically = aInit.mCreatedDynamically; + context->mChildOffset = aInit.mChildOffset; + if (context->GetHasSessionHistory()) { + context->CreateChildSHistory(); + if (mozilla::SessionHistoryInParent()) { + context->GetChildSessionHistory()->SetIndexAndLength( + aInit.mSessionHistoryIndex, aInit.mSessionHistoryCount, nsID()); + } + } + + // NOTE: Call through the `Set` methods for these values to ensure that any + // relevant process-local state is also updated. + context->SetOriginAttributes(aInit.mOriginAttributes); + context->SetRemoteTabs(aInit.mUseRemoteTabs); + context->SetRemoteSubframes(aInit.mUseRemoteSubframes); + context->mRequestContextId = aInit.mRequestContextId; + // NOTE: Private browsing ID is set by `SetOriginAttributes`. + + Register(context); + + return context->Attach(/* aFromIPC */ true, aOriginProcess); +} + +BrowsingContext::BrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, Type aType, + FieldValues&& aInit) + : mFields(std::move(aInit)), + mType(aType), + mBrowsingContextId(aBrowsingContextId), + mGroup(aGroup), + mParentWindow(aParentWindow), + mPrivateBrowsingId(0), + mEverAttached(false), + mIsInProcess(false), + mIsDiscarded(false), + mWindowless(false), + mDanglingRemoteOuterProxies(false), + mEmbeddedByThisProcess(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mCreatedDynamically(false), + mIsInBFCache(false), + mCanExecuteScripts(true), + mChildOffset(0) { + MOZ_RELEASE_ASSERT(!mParentWindow || mParentWindow->Group() == mGroup); + MOZ_RELEASE_ASSERT(mBrowsingContextId != 0); + MOZ_RELEASE_ASSERT(mGroup); +} + +void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) { + // XXX(nika): We should communicate that we are now an active BrowsingContext + // process to the parent & do other validation here. + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_RELEASE_ASSERT(aDocShell->GetBrowsingContext() == this); + mDocShell = aDocShell; + mDanglingRemoteOuterProxies = !mIsInProcess; + mIsInProcess = true; + if (mChildSessionHistory) { + mChildSessionHistory->SetIsInProcess(true); + } + + RecomputeCanExecuteScripts(); +} + +// This class implements a callback that will return the remote window proxy for +// mBrowsingContext in that compartment, if it has one. It also removes the +// proxy from the map, because the object will be transplanted into another kind +// of object. +class MOZ_STACK_CLASS CompartmentRemoteProxyTransplantCallback + : public js::CompartmentTransplantCallback { + public: + explicit CompartmentRemoteProxyTransplantCallback( + BrowsingContext* aBrowsingContext) + : mBrowsingContext(aBrowsingContext) {} + + virtual JSObject* getObjectToTransplant( + JS::Compartment* compartment) override { + auto* priv = xpc::CompartmentPrivate::Get(compartment); + if (!priv) { + return nullptr; + } + + auto& map = priv->GetRemoteProxyMap(); + auto result = map.lookup(mBrowsingContext); + if (!result) { + return nullptr; + } + JSObject* resultObject = result->value(); + map.remove(result); + + return resultObject; + } + + private: + BrowsingContext* mBrowsingContext; +}; + +void BrowsingContext::CleanUpDanglingRemoteOuterWindowProxies( + JSContext* aCx, JS::MutableHandle aOuter) { + if (!mDanglingRemoteOuterProxies) { + return; + } + mDanglingRemoteOuterProxies = false; + + CompartmentRemoteProxyTransplantCallback cb(this); + js::RemapRemoteWindowProxies(aCx, &cb, aOuter); +} + +bool BrowsingContext::IsActive() const { + const BrowsingContext* current = this; + do { + auto explicit_ = current->GetExplicitActive(); + if (explicit_ != ExplicitActiveStatus::None) { + return explicit_ == ExplicitActiveStatus::Active; + } + if (mParentWindow && !mParentWindow->IsCurrent()) { + return false; + } + } while ((current = current->GetParent())); + + return false; +} + +bool BrowsingContext::GetIsActiveBrowserWindow() { + if (!XRE_IsParentProcess()) { + return Top()->GetIsActiveBrowserWindowInternal(); + } + + // chrome:// urls loaded in the parent won't receive + // their own activation so we defer to the top chrome + // Browsing Context when in the parent process. + RefPtr chromeTop = + Canonical()->TopCrossChromeBoundary(); + return chromeTop->GetIsActiveBrowserWindowInternal(); +} + +void BrowsingContext::SetIsActiveBrowserWindow(bool aActive) { + Unused << SetIsActiveBrowserWindowInternal(aActive); +} + +bool BrowsingContext::FullscreenAllowed() const { + for (auto* current = this; current; current = current->GetParent()) { + if (!current->GetFullscreenAllowedByOwner()) { + return false; + } + } + return true; +} + +static bool OwnerAllowsFullscreen(const Element& aEmbedder) { + if (aEmbedder.IsXULElement()) { + return !aEmbedder.HasAttr(nsGkAtoms::disablefullscreen); + } + if (aEmbedder.IsHTMLElement(nsGkAtoms::iframe)) { + // This is controlled by feature policy. + return true; + } + if (const auto* embed = HTMLEmbedElement::FromNode(aEmbedder)) { + return embed->AllowFullscreen(); + } + return false; +} + +void BrowsingContext::SetEmbedderElement(Element* aEmbedder) { + mEmbeddedByThisProcess = true; + + // Update embedder-element-specific fields in a shared transaction. + // Don't do this when clearing our embedder, as we're being destroyed either + // way. + if (aEmbedder) { + Transaction txn; + txn.SetEmbedderElementType(Some(aEmbedder->LocalName())); + txn.SetEmbeddedInContentDocument( + aEmbedder->OwnerDoc()->IsContentDocument()); + if (nsCOMPtr inner = + do_QueryInterface(aEmbedder->GetOwnerGlobal())) { + txn.SetEmbedderInnerWindowId(inner->WindowID()); + } + txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder)); + if (XRE_IsParentProcess() && IsTopContent()) { + nsAutoString messageManagerGroup; + if (aEmbedder->IsXULElement()) { + aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup); + if (!aEmbedder->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::initiallyactive, + nsGkAtoms::_false, eIgnoreCase)) { + txn.SetExplicitActive(ExplicitActiveStatus::Active); + } + } + txn.SetMessageManagerGroup(messageManagerGroup); + + bool useGlobalHistory = + !aEmbedder->HasAttr(nsGkAtoms::disableglobalhistory); + txn.SetUseGlobalHistory(useGlobalHistory); + } + + MOZ_ALWAYS_SUCCEEDS(txn.Commit(this)); + } + + if (XRE_IsParentProcess() && IsTopContent()) { + Canonical()->MaybeSetPermanentKey(aEmbedder); + } + + mEmbedderElement = aEmbedder; + + if (mEmbedderElement) { + if (nsCOMPtr obs = services::GetObserverService()) { + obs->NotifyWhenScriptSafe(ToSupports(this), + "browsing-context-did-set-embedder", nullptr); + } + + if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument() && + IsEmbedderTypeObjectOrEmbed()) { + Unused << SetSyntheticDocumentContainer(true); + } + } +} + +bool BrowsingContext::IsEmbedderTypeObjectOrEmbed() { + if (const Maybe& type = GetEmbedderElementType()) { + return nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type); + } + return false; +} + +void BrowsingContext::Embed() { + if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) { + frame->BindToBrowsingContext(this); + } +} + +mozilla::ipc::IPCResult BrowsingContext::Attach(bool aFromIPC, + ContentParent* aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT(!mEverAttached); + MOZ_DIAGNOSTIC_ASSERT_IF(aFromIPC, aOriginProcess || XRE_IsContentProcess()); + mEverAttached = true; + + if (MOZ_LOG_TEST(GetLog(), LogLevel::Debug)) { + nsAutoCString suffix; + mOriginAttributes.CreateSuffix(suffix); + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Connecting 0x%08" PRIx64 " to 0x%08" PRIx64 + " (private=%d, remote=%d, fission=%d, oa=%s)", + XRE_IsParentProcess() ? "Parent" : "Child", Id(), + GetParent() ? GetParent()->Id() : 0, (int)mPrivateBrowsingId, + (int)mUseRemoteTabs, (int)mUseRemoteSubframes, suffix.get())); + } + + MOZ_DIAGNOSTIC_ASSERT(mGroup); + MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); + + if (mGroup->IsPotentiallyCrossOriginIsolated() != + (Top()->GetOpenerPolicy() == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP)) { + MOZ_DIAGNOSTIC_ASSERT(aFromIPC); + if (aFromIPC) { + auto* actor = aOriginProcess + ? static_cast(aOriginProcess) + : static_cast( + ContentChild::GetSingleton()); + return IPC_FAIL( + actor, + "Invalid CrossOriginIsolated state in BrowsingContext::Attach call"); + } else { + MOZ_CRASH( + "Invalid CrossOriginIsolated state in BrowsingContext::Attach call"); + } + } + + AssertCoherentLoadContext(); + + // Add ourselves either to our parent or BrowsingContextGroup's child list. + // Important: We shouldn't return IPC_FAIL after this point, since the + // BrowsingContext will have already been added to the tree. + if (mParentWindow) { + if (!aFromIPC) { + MOZ_DIAGNOSTIC_ASSERT(!mParentWindow->IsDiscarded(), + "local attach in discarded window"); + MOZ_DIAGNOSTIC_ASSERT(!GetParent()->IsDiscarded(), + "local attach call in discarded bc"); + MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild(), + "local attach call with oop parent window"); + MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild()->CanSend(), + "local attach call with dead parent window"); + } + mChildOffset = + mCreatedDynamically ? -1 : mParentWindow->Children().Length(); + mParentWindow->AppendChildBrowsingContext(this); + RecomputeCanExecuteScripts(); + } else { + mGroup->Toplevels().AppendElement(this); + } + + if (GetIsPopupSpam()) { + PopupBlocker::RegisterOpenPopupSpam(); + } + + if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) { + CreateChildSHistory(); + } + + // Why the context is being attached. This will always be "attach" in the + // content process, but may be "replace" if it's known the context being + // replaced in the parent process. + const char16_t* why = u"attach"; + + if (XRE_IsContentProcess() && !aFromIPC) { + // Send attach to our parent if we need to. + ContentChild::GetSingleton()->SendCreateBrowsingContext( + mGroup->Id(), GetIPCInitializer()); + } else if (XRE_IsParentProcess()) { + // If this window was created as a subframe by a content process, it must be + // being hosted within the same BrowserParent as its mParentWindow. + // Toplevel BrowsingContexts created by content have their BrowserParent + // configured during `RecvConstructPopupBrowser`. + if (mParentWindow && aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT( + mParentWindow->Canonical()->GetContentParent() == aOriginProcess, + "Creator process isn't the same as our embedder?"); + Canonical()->SetCurrentBrowserParent( + mParentWindow->Canonical()->GetBrowserParent()); + } + + mGroup->EachOtherParent(aOriginProcess, [&](ContentParent* aParent) { + MOZ_DIAGNOSTIC_ASSERT(IsContent(), + "chrome BCG cannot be synced to content process"); + if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID())) { + Unused << aParent->SendCreateBrowsingContext(mGroup->Id(), + GetIPCInitializer()); + } + }); + + if (IsTop() && IsContent() && Canonical()->GetWebProgress()) { + why = u"replace"; + } + + // We want to create a BrowsingContextWebProgress for all content + // BrowsingContexts. + if (IsContent() && !Canonical()->mWebProgress) { + Canonical()->mWebProgress = new BrowsingContextWebProgress(Canonical()); + } + } + + if (nsCOMPtr obs = services::GetObserverService()) { + obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached", + why); + } + + if (XRE_IsParentProcess()) { + Canonical()->CanonicalAttach(); + } + return IPC_OK(); +} + +void BrowsingContext::Detach(bool aFromIPC) { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id(), + GetParent() ? GetParent()->Id() : 0)); + + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); + + if (XRE_IsParentProcess()) { + Canonical()->AddPendingDiscard(); + } + auto callListeners = + MakeScopeExit([&, listeners = std::move(mDiscardListeners), id = Id()] { + for (const auto& listener : listeners) { + listener(id); + } + if (XRE_IsParentProcess()) { + Canonical()->RemovePendingDiscard(); + } + }); + + nsCOMPtr rcsvc = + net::RequestContextService::GetOrCreate(); + if (rcsvc) { + rcsvc->RemoveRequestContext(GetRequestContextId()); + } + + // This will only ever be null if the cycle-collector has unlinked us. Don't + // try to detach ourselves in that case. + if (NS_WARN_IF(!mGroup)) { + MOZ_ASSERT_UNREACHABLE(); + return; + } + + if (mParentWindow) { + mParentWindow->RemoveChildBrowsingContext(this); + } else { + mGroup->Toplevels().RemoveElement(this); + } + + if (XRE_IsParentProcess()) { + RefPtr self{Canonical()}; + Group()->EachParent([&](ContentParent* aParent) { + // Only the embedder process is allowed to initiate a BrowsingContext + // detach, so if we've gotten here, the host process already knows we've + // been detached, and there's no need to tell it again. + // + // If the owner process is not the same as the embedder process, its + // BrowsingContext will be detached when its nsWebBrowser instance is + // destroyed. + bool doDiscard = !Canonical()->IsEmbeddedInProcess(aParent->ChildID()) && + !Canonical()->IsOwnedByProcess(aParent->ChildID()); + + // Hold a strong reference to ourself, and keep our BrowsingContextGroup + // alive, until the responses comes back to ensure we don't die while + // messages relating to this context are in-flight. + // + // When the callback is called, the keepalive on our group will be + // destroyed, and the reference to the BrowsingContext will be dropped, + // which may cause it to be fully destroyed. + mGroup->AddKeepAlive(); + self->AddPendingDiscard(); + auto callback = [self](auto) { + self->mGroup->RemoveKeepAlive(); + self->RemovePendingDiscard(); + }; + + aParent->SendDiscardBrowsingContext(this, doDiscard, callback, callback); + }); + } else { + // Hold a strong reference to ourself until the responses come back to + // ensure the BrowsingContext isn't cleaned up before the parent process + // acknowledges the discard request. + auto callback = [self = RefPtr{this}](auto) {}; + ContentChild::GetSingleton()->SendDiscardBrowsingContext( + this, !aFromIPC, callback, callback); + } + + mGroup->Unregister(this); + UnregisterBrowserId(this); + mIsDiscarded = true; + + if (XRE_IsParentProcess()) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->BrowsingContextDetached(this); + } + } + + if (nsCOMPtr obs = services::GetObserverService()) { + // Why the context is being discarded. This will always be "discard" in the + // content process, but may be "replace" if it's known the context being + // replaced in the parent process. + const char16_t* why = u"discard"; + if (XRE_IsParentProcess() && IsTop() && !Canonical()->GetWebProgress()) { + why = u"replace"; + } + obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", why); + } + + // NOTE: Doesn't use SetClosed, as it will be set in all processes + // automatically by calls to Detach() + mFields.SetWithoutSyncing(true); + + if (GetIsPopupSpam()) { + PopupBlocker::UnregisterOpenPopupSpam(); + // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes + // automatically. + mFields.SetWithoutSyncing(false); + } + + AssertOriginAttributesMatchPrivateBrowsing(); + + if (XRE_IsParentProcess()) { + Canonical()->CanonicalDiscard(); + } +} + +void BrowsingContext::AddDiscardListener( + std::function&& aListener) { + if (mIsDiscarded) { + aListener(Id()); + return; + } + mDiscardListeners.AppendElement(std::move(aListener)); +} + +void BrowsingContext::PrepareForProcessChange() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Preparing 0x%08" PRIx64 " for a process change", + XRE_IsParentProcess() ? "Parent" : "Child", Id())); + + MOZ_ASSERT(mIsInProcess, "Must currently be an in-process frame"); + MOZ_ASSERT(!mIsDiscarded, "We're already closed?"); + + mIsInProcess = false; + mUserGestureStart = TimeStamp(); + + // NOTE: For now, clear our nsDocShell reference, as we're primarily in a + // different process now. This may need to change in the future with + // Cross-Process BFCache. + mDocShell = nullptr; + if (mChildSessionHistory) { + // This can be removed once session history is stored exclusively in the + // parent process. + mChildSessionHistory->SetIsInProcess(false); + } + + if (!mWindowProxy) { + return; + } + + // We have to go through mWindowProxy rather than calling GetDOMWindow() on + // mDocShell because the mDocshell reference gets cleared immediately after + // the window is closed. + nsGlobalWindowOuter::PrepareForProcessChange(mWindowProxy); + MOZ_ASSERT(!mWindowProxy); +} + +bool BrowsingContext::IsTargetable() const { + return !GetClosed() && AncestorsAreCurrent(); +} + +bool BrowsingContext::HasOpener() const { + return sBrowsingContexts->Contains(GetOpenerId()); +} + +bool BrowsingContext::AncestorsAreCurrent() const { + const BrowsingContext* bc = this; + while (true) { + if (bc->IsDiscarded()) { + return false; + } + + if (WindowContext* wc = bc->GetParentWindowContext()) { + if (!wc->IsCurrent() || wc->IsDiscarded()) { + return false; + } + + bc = wc->GetBrowsingContext(); + } else { + return true; + } + } +} + +bool BrowsingContext::IsInBFCache() const { + if (mozilla::SessionHistoryInParent()) { + return mIsInBFCache; + } + return mParentWindow && + mParentWindow->TopWindowContext()->GetWindowStateSaved(); +} + +Span> BrowsingContext::Children() const { + if (WindowContext* current = mCurrentWindowContext) { + return current->Children(); + } + return Span>(); +} + +void BrowsingContext::GetChildren( + nsTArray>& aChildren) { + aChildren.AppendElements(Children()); +} + +Span> BrowsingContext::NonSyntheticChildren() const { + if (WindowContext* current = mCurrentWindowContext) { + return current->NonSyntheticChildren(); + } + return Span>(); +} + +void BrowsingContext::GetWindowContexts( + nsTArray>& aWindows) { + aWindows.AppendElements(mWindowContexts); +} + +void BrowsingContext::RegisterWindowContext(WindowContext* aWindow) { + MOZ_ASSERT(!mWindowContexts.Contains(aWindow), + "WindowContext already registered!"); + MOZ_ASSERT(aWindow->GetBrowsingContext() == this); + + mWindowContexts.AppendElement(aWindow); + + // If the newly registered WindowContext is for our current inner window ID, + // re-run the `DidSet` handler to re-establish the relationship. + if (aWindow->InnerWindowId() == GetCurrentInnerWindowId()) { + DidSet(FieldIndex()); + MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == aWindow); + } +} + +void BrowsingContext::UnregisterWindowContext(WindowContext* aWindow) { + MOZ_ASSERT(mWindowContexts.Contains(aWindow), + "WindowContext not registered!"); + mWindowContexts.RemoveElement(aWindow); + + // If our currently active window was unregistered, clear our reference to it. + if (aWindow == mCurrentWindowContext) { + // Re-read our `CurrentInnerWindowId` value and use it to set + // `mCurrentWindowContext`. As `aWindow` is now unregistered and discarded, + // we won't find it, and the value will be cleared back to `nullptr`. + DidSet(FieldIndex()); + MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr); + } +} + +void BrowsingContext::PreOrderWalkVoid( + const std::function& aCallback) { + aCallback(this); + + AutoTArray, 8> children; + children.AppendElements(Children()); + + for (auto& child : children) { + child->PreOrderWalkVoid(aCallback); + } +} + +BrowsingContext::WalkFlag BrowsingContext::PreOrderWalkFlag( + const std::function& aCallback) { + switch (aCallback(this)) { + case WalkFlag::Skip: + return WalkFlag::Next; + case WalkFlag::Stop: + return WalkFlag::Stop; + case WalkFlag::Next: + default: + break; + } + + AutoTArray, 8> children; + children.AppendElements(Children()); + + for (auto& child : children) { + switch (child->PreOrderWalkFlag(aCallback)) { + case WalkFlag::Stop: + return WalkFlag::Stop; + default: + break; + } + } + + return WalkFlag::Next; +} + +void BrowsingContext::PostOrderWalk( + const std::function& aCallback) { + AutoTArray, 8> children; + children.AppendElements(Children()); + + for (auto& child : children) { + child->PostOrderWalk(aCallback); + } + + aCallback(this); +} + +void BrowsingContext::GetAllBrowsingContextsInSubtree( + nsTArray>& aBrowsingContexts) { + PreOrderWalk([&](BrowsingContext* aContext) { + aBrowsingContexts.AppendElement(aContext); + }); +} + +BrowsingContext* BrowsingContext::FindChildWithName( + const nsAString& aName, WindowGlobalChild& aRequestingWindow) { + if (aName.IsEmpty()) { + // You can't find a browsing context with the empty name. + return nullptr; + } + + for (BrowsingContext* child : NonSyntheticChildren()) { + if (child->NameEquals(aName) && aRequestingWindow.CanNavigate(child) && + child->IsTargetable()) { + return child; + } + } + + return nullptr; +} + +BrowsingContext* BrowsingContext::FindWithSpecialName( + const nsAString& aName, WindowGlobalChild& aRequestingWindow) { + // TODO(farre): Neither BrowsingContext nor nsDocShell checks if the + // browsing context pointed to by a special name is active. Should + // it be? See Bug 1527913. + if (aName.LowerCaseEqualsLiteral("_self")) { + return this; + } + + if (aName.LowerCaseEqualsLiteral("_parent")) { + if (BrowsingContext* parent = GetParent()) { + return aRequestingWindow.CanNavigate(parent) ? parent : nullptr; + } + return this; + } + + if (aName.LowerCaseEqualsLiteral("_top")) { + BrowsingContext* top = Top(); + + return aRequestingWindow.CanNavigate(top) ? top : nullptr; + } + + return nullptr; +} + +BrowsingContext* BrowsingContext::FindWithNameInSubtree( + const nsAString& aName, WindowGlobalChild* aRequestingWindow) { + MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty()); + + if (NameEquals(aName) && + (!aRequestingWindow || aRequestingWindow->CanNavigate(this)) && + IsTargetable()) { + return this; + } + + for (BrowsingContext* child : NonSyntheticChildren()) { + if (BrowsingContext* found = + child->FindWithNameInSubtree(aName, aRequestingWindow)) { + return found; + } + } + + return nullptr; +} + +bool BrowsingContext::IsSandboxedFrom(BrowsingContext* aTarget) { + // If no target then not sandboxed. + if (!aTarget) { + return false; + } + + // We cannot be sandboxed from ourselves. + if (aTarget == this) { + return false; + } + + // Default the sandbox flags to our flags, so that if we can't retrieve the + // active document, we will still enforce our own. + uint32_t sandboxFlags = GetSandboxFlags(); + if (mDocShell) { + if (RefPtr doc = mDocShell->GetExtantDocument()) { + sandboxFlags = doc->GetSandboxFlags(); + } + } + + // If no flags, we are not sandboxed at all. + if (!sandboxFlags) { + return false; + } + + // If aTarget has an ancestor, it is not top level. + if (RefPtr ancestorOfTarget = aTarget->GetParent()) { + do { + // We are not sandboxed if we are an ancestor of target. + if (ancestorOfTarget == this) { + return false; + } + ancestorOfTarget = ancestorOfTarget->GetParent(); + } while (ancestorOfTarget); + + // Otherwise, we are sandboxed from aTarget. + return true; + } + + // aTarget is top level, are we the "one permitted sandboxed + // navigator", i.e. did we open aTarget? + if (aTarget->GetOnePermittedSandboxedNavigatorId() == Id()) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed + // from our top. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) && aTarget == Top()) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION flag is not on, we are not + // sandboxed from our top if we have user interaction. We assume there is a + // valid transient user gesture interaction if this check happens in the + // target process given that we have checked in the triggering process + // already. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) && + mCurrentWindowContext && + (!mCurrentWindowContext->IsInProcess() || + mCurrentWindowContext->HasValidTransientUserGestureActivation()) && + aTarget == Top()) { + return false; + } + + // Otherwise, we are sandboxed from aTarget. + return true; +} + +RefPtr BrowsingContext::GetSessionStorageManager() { + RefPtr& manager = Top()->mSessionStorageManager; + if (!manager) { + manager = new SessionStorageManager(this); + } + return manager; +} + +bool BrowsingContext::CrossOriginIsolated() { + MOZ_ASSERT(NS_IsMainThread()); + + return StaticPrefs:: + dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup() && + Top()->GetOpenerPolicy() == + nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP && + XRE_IsContentProcess() && + StringBeginsWith(ContentChild::GetSingleton()->GetRemoteType(), + WITH_COOP_COEP_REMOTE_TYPE_PREFIX); +} + +void BrowsingContext::SetTriggeringAndInheritPrincipals( + nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, + uint64_t aLoadIdentifier) { + mTriggeringPrincipal = Some( + PrincipalWithLoadIdentifierTuple(aTriggeringPrincipal, aLoadIdentifier)); + if (aPrincipalToInherit) { + mPrincipalToInherit = Some( + PrincipalWithLoadIdentifierTuple(aPrincipalToInherit, aLoadIdentifier)); + } +} + +std::tuple, nsCOMPtr> +BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() { + nsCOMPtr triggeringPrincipal = + GetSavedPrincipal(mTriggeringPrincipal); + nsCOMPtr principalToInherit = + GetSavedPrincipal(mPrincipalToInherit); + return std::make_tuple(triggeringPrincipal, principalToInherit); +} + +nsIPrincipal* BrowsingContext::GetSavedPrincipal( + Maybe aPrincipalTuple) { + if (aPrincipalTuple) { + nsCOMPtr principal; + uint64_t loadIdentifier; + std::tie(principal, loadIdentifier) = *aPrincipalTuple; + // We want to return a principal only if the load identifier for it + // matches the current one for this BC. + if (auto current = GetCurrentLoadIdentifier(); + current && *current == loadIdentifier) { + return principal; + } + } + return nullptr; +} + +BrowsingContext::~BrowsingContext() { + MOZ_DIAGNOSTIC_ASSERT(!mParentWindow || + !mParentWindow->mChildren.Contains(this)); + MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this)); + + mDeprioritizedLoadRunner.clear(); + + if (sBrowsingContexts) { + sBrowsingContexts->Remove(Id()); + } + UnregisterBrowserId(this); +} + +/* static */ +void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (sBrowsingContexts) { + AutoTArray, 8> toDiscard; + for (const auto& data : sBrowsingContexts->Values()) { + auto* bc = data->Canonical(); + if (!bc->IsDiscarded() && bc->IsEmbeddedInProcess(aCP->ChildID())) { + toDiscard.AppendElement(bc); + } + } + + for (BrowsingContext* bc : toDiscard) { + bc->Detach(/* aFromIPC */ true); + } + } +} + +nsISupports* BrowsingContext::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* BrowsingContext::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto); +} + +bool BrowsingContext::WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) { + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + return (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BROWSING_CONTEXT, 0) && + JS_WriteUint32Pair(aWriter, uint32_t(Id()), uint32_t(Id() >> 32))); +} + +/* static */ +JSObject* BrowsingContext::ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + StructuredCloneHolder* aHolder) { + uint32_t idLow = 0; + uint32_t idHigh = 0; + if (!JS_ReadUint32Pair(aReader, &idLow, &idHigh)) { + return nullptr; + } + uint64_t id = uint64_t(idHigh) << 32 | idLow; + + // Note: Do this check after reading our ID data. Returning null will abort + // the decode operation anyway, but we should at least be as safe as possible. + if (NS_WARN_IF(!NS_IsMainThread())) { + MOZ_DIAGNOSTIC_ASSERT(false, + "We shouldn't be trying to decode a BrowsingContext " + "on a background thread."); + return nullptr; + } + + JS::Rooted val(aCx, JS::NullValue()); + // We'll get rooting hazard errors from the RefPtr destructor if it isn't + // destroyed before we try to return a raw JSObject*, so create it in its own + // scope. + if (RefPtr context = Get(id)) { + if (!GetOrCreateDOMReflector(aCx, context, &val) || !val.isObject()) { + return nullptr; + } + } + return val.toObjectOrNull(); +} + +bool BrowsingContext::CanSetOriginAttributes() { + // A discarded BrowsingContext has already been destroyed, and cannot modify + // its OriginAttributes. + if (NS_WARN_IF(IsDiscarded())) { + return false; + } + + // Before attaching is the safest time to set OriginAttributes, and the only + // allowed time for content BrowsingContexts. + if (!EverAttached()) { + return true; + } + + // Attached content BrowsingContexts may have been synced to other processes. + if (NS_WARN_IF(IsContent())) { + MOZ_CRASH(); + return false; + } + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + // Cannot set OriginAttributes after we've created our child BrowsingContext. + if (NS_WARN_IF(!Children().IsEmpty())) { + return false; + } + + // Only allow setting OriginAttributes if we have no associated document, or + // the document is still `about:blank`. + // TODO: Bug 1273058 - should have no document when setting origin attributes. + if (WindowGlobalParent* window = Canonical()->GetCurrentWindowGlobal()) { + if (nsIURI* uri = window->GetDocumentURI()) { + MOZ_ASSERT(NS_IsAboutBlank(uri)); + return NS_IsAboutBlank(uri); + } + } + return true; +} + +Nullable BrowsingContext::GetAssociatedWindow() { + // nsILoadContext usually only returns same-process windows, + // so we intentionally return nullptr if this BC is out of + // process. + if (IsInProcess()) { + return WindowProxyHolder(this); + } + return nullptr; +} + +Nullable BrowsingContext::GetTopWindow() { + return Top()->GetAssociatedWindow(); +} + +Element* BrowsingContext::GetTopFrameElement() { + return Top()->GetEmbedderElement(); +} + +void BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing, + ErrorResult& aError) { + nsresult rv = SetUsePrivateBrowsing(aUsePrivateBrowsing); + if (NS_FAILED(rv)) { + aError.Throw(rv); + } +} + +void BrowsingContext::SetUseTrackingProtectionWebIDL( + bool aUseTrackingProtection, ErrorResult& aRv) { + SetForceEnableTrackingProtection(aUseTrackingProtection, aRv); +} + +void BrowsingContext::GetOriginAttributes(JSContext* aCx, + JS::MutableHandle aVal, + ErrorResult& aError) { + AssertOriginAttributesMatchPrivateBrowsing(); + + if (!ToJSValue(aCx, mOriginAttributes, aVal)) { + aError.NoteJSContextException(aCx); + } +} + +NS_IMETHODIMP BrowsingContext::GetAssociatedWindow( + mozIDOMWindowProxy** aAssociatedWindow) { + nsCOMPtr win = GetDOMWindow(); + win.forget(aAssociatedWindow); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetTopWindow(mozIDOMWindowProxy** aTopWindow) { + return Top()->GetAssociatedWindow(aTopWindow); +} + +NS_IMETHODIMP BrowsingContext::GetTopFrameElement(Element** aTopFrameElement) { + RefPtr topFrameElement = GetTopFrameElement(); + topFrameElement.forget(aTopFrameElement); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetIsContent(bool* aIsContent) { + *aIsContent = IsContent(); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUsePrivateBrowsing( + bool* aUsePrivateBrowsing) { + *aUsePrivateBrowsing = mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { + if (!CanSetOriginAttributes()) { + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); + if (changed) { + NS_WARNING("SetUsePrivateBrowsing when !CanSetOriginAttributes()"); + } + return changed ? NS_ERROR_FAILURE : NS_OK; + } + + return SetPrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP BrowsingContext::SetPrivateBrowsing(bool aPrivateBrowsing) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set PrivateBrowsing when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + bool changed = aPrivateBrowsing != (mPrivateBrowsingId > 0); + if (changed) { + mPrivateBrowsingId = aPrivateBrowsing ? 1 : 0; + if (IsContent()) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(aPrivateBrowsing); + } + + if (XRE_IsParentProcess()) { + Canonical()->AdjustPrivateBrowsingCount(aPrivateBrowsing); + } + } + AssertOriginAttributesMatchPrivateBrowsing(); + + if (changed && mDocShell) { + nsDocShell::Cast(mDocShell)->NotifyPrivateBrowsingChanged(); + } + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUseRemoteTabs(bool* aUseRemoteTabs) { + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetRemoteTabs(bool aUseRemoteTabs) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set RemoteTabs when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + static bool annotated = false; + if (aUseRemoteTabs && !annotated) { + annotated = true; + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::DOMIPCEnabled, + true); + } + + // Don't allow non-remote tabs with remote subframes. + if (NS_WARN_IF(!aUseRemoteTabs && mUseRemoteSubframes)) { + return NS_ERROR_UNEXPECTED; + } + + mUseRemoteTabs = aUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUseRemoteSubframes( + bool* aUseRemoteSubframes) { + *aUseRemoteSubframes = mUseRemoteSubframes; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetRemoteSubframes(bool aUseRemoteSubframes) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set RemoteSubframes when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + static bool annotated = false; + if (aUseRemoteSubframes && !annotated) { + annotated = true; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::DOMFissionEnabled, true); + } + + // Don't allow non-remote tabs with remote subframes. + if (NS_WARN_IF(aUseRemoteSubframes && !mUseRemoteTabs)) { + return NS_ERROR_UNEXPECTED; + } + + mUseRemoteSubframes = aUseRemoteSubframes; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUseTrackingProtection( + bool* aUseTrackingProtection) { + *aUseTrackingProtection = false; + + if (GetForceEnableTrackingProtection() || + StaticPrefs::privacy_trackingprotection_enabled() || + (UsePrivateBrowsing() && + StaticPrefs::privacy_trackingprotection_pbmode_enabled())) { + *aUseTrackingProtection = true; + return NS_OK; + } + + if (GetParent()) { + return GetParent()->GetUseTrackingProtection(aUseTrackingProtection); + } + + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetUseTrackingProtection( + bool aUseTrackingProtection) { + return SetForceEnableTrackingProtection(aUseTrackingProtection); +} + +NS_IMETHODIMP BrowsingContext::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle aVal) { + AssertOriginAttributesMatchPrivateBrowsing(); + + bool ok = ToJSValue(aCx, mOriginAttributes, aVal); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP_(void) +BrowsingContext::GetOriginAttributes(OriginAttributes& aAttrs) { + aAttrs = mOriginAttributes; + AssertOriginAttributesMatchPrivateBrowsing(); +} + +nsresult BrowsingContext::SetOriginAttributes(const OriginAttributes& aAttrs) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set OriginAttributes when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + AssertOriginAttributesMatchPrivateBrowsing(); + mOriginAttributes = aAttrs; + + bool isPrivate = mOriginAttributes.mPrivateBrowsingId != + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; + // Chrome Browsing Context can not contain OriginAttributes.mPrivateBrowsingId + if (IsChrome() && isPrivate) { + mOriginAttributes.mPrivateBrowsingId = + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; + } + SetPrivateBrowsing(isPrivate); + AssertOriginAttributesMatchPrivateBrowsing(); + + return NS_OK; +} + +void BrowsingContext::AssertCoherentLoadContext() { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // LoadContext should generally match our opener or parent. + if (IsContent()) { + if (RefPtr opener = GetOpener()) { + MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType); + MOZ_DIAGNOSTIC_ASSERT(opener->mGroup == mGroup); + MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteTabs == mUseRemoteTabs); + MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteSubframes == mUseRemoteSubframes); + MOZ_DIAGNOSTIC_ASSERT(opener->mPrivateBrowsingId == mPrivateBrowsingId); + MOZ_DIAGNOSTIC_ASSERT( + opener->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes)); + } + } + if (RefPtr parent = GetParent()) { + MOZ_DIAGNOSTIC_ASSERT(parent->mType == mType); + MOZ_DIAGNOSTIC_ASSERT(parent->mGroup == mGroup); + MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteTabs == mUseRemoteTabs); + MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteSubframes == mUseRemoteSubframes); + MOZ_DIAGNOSTIC_ASSERT(parent->mPrivateBrowsingId == mPrivateBrowsingId); + MOZ_DIAGNOSTIC_ASSERT( + parent->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes)); + } + + // UseRemoteSubframes and UseRemoteTabs must match. + MOZ_DIAGNOSTIC_ASSERT( + !mUseRemoteSubframes || mUseRemoteTabs, + "Cannot set useRemoteSubframes without also setting useRemoteTabs"); + + // Double-check OriginAttributes/Private Browsing + AssertOriginAttributesMatchPrivateBrowsing(); +#endif +} + +void BrowsingContext::AssertOriginAttributesMatchPrivateBrowsing() { + // Chrome browsing contexts must not have a private browsing OriginAttribute + // Content browsing contexts must maintain the equality: + // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId + if (IsChrome()) { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0); + } else { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == + mPrivateBrowsingId); + } +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContext) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsILoadContext) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(BrowsingContext) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContext) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContext) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(BrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowsingContext) + if (sBrowsingContexts) { + sBrowsingContexts->Remove(tmp->Id()); + } + UnregisterBrowserId(tmp); + + if (tmp->GetIsPopupSpam()) { + PopupBlocker::UnregisterOpenPopupSpam(); + // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes + // automatically. + tmp->mFields.SetWithoutSyncing(false); + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK( + mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts, + mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( + mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts, + mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +static bool IsCertainlyAliveForCC(BrowsingContext* aContext) { + return aContext->HasKnownLiveWrapper() || + (AppShutdown::GetCurrentShutdownPhase() == + ShutdownPhase::NotInShutdown && + aContext->EverAttached() && !aContext->IsDiscarded()); +} + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(BrowsingContext) + if (IsCertainlyAliveForCC(tmp)) { + if (tmp->PreservingWrapper()) { + tmp->MarkWrapperLive(); + } + return true; + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(BrowsingContext) + return IsCertainlyAliveForCC(tmp) && tmp->HasNothingToTrace(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(BrowsingContext) + return IsCertainlyAliveForCC(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +class RemoteLocationProxy + : public RemoteObjectProxy { + public: + typedef RemoteObjectProxy Base; + + constexpr RemoteLocationProxy() + : RemoteObjectProxy(prototypes::id::Location) {} + + void NoteChildren(JSObject* aProxy, + nsCycleCollectionTraversalCallback& aCb) const override { + auto location = + static_cast(GetNative(aProxy)); + CycleCollectionNoteChild(aCb, location->GetBrowsingContext(), + "JS::GetPrivate(obj)->GetBrowsingContext()"); + } +}; + +static const RemoteLocationProxy sSingleton; + +// Give RemoteLocationProxy 2 reserved slots, like the other wrappers, +// so JSObject::swap can swap it with CrossCompartmentWrappers without requiring +// malloc. +template <> +const JSClass RemoteLocationProxy::Base::sClass = + PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); + +void BrowsingContext::Location(JSContext* aCx, + JS::MutableHandle aLocation, + ErrorResult& aError) { + aError.MightThrowJSException(); + sSingleton.GetProxyObject(aCx, &mLocation, /* aTransplantTo = */ nullptr, + aLocation); + if (!aLocation) { + aError.StealExceptionFromJSContext(aCx); + } +} + +bool BrowsingContext::RemoveRootFromBFCacheSync() { + if (WindowContext* wc = GetParentWindowContext()) { + if (RefPtr doc = wc->TopWindowContext()->GetDocument()) { + return doc->RemoveFromBFCacheSync(); + } + } + return false; +} + +nsresult BrowsingContext::CheckSandboxFlags(nsDocShellLoadState* aLoadState) { + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + if (sourceBC.IsNull()) { + return NS_OK; + } + + // We might be called after the source BC has been discarded, but before we've + // destroyed our in-process instance of the BrowsingContext object in some + // situations (e.g. after creating a new pop-up with window.open while the + // window is being closed). In these situations we want to still perform the + // sandboxing check against our in-process copy. If we've forgotten about the + // context already, assume it is sanboxed. (bug 1643450) + BrowsingContext* bc = sourceBC.GetMaybeDiscarded(); + if (!bc || bc->IsSandboxedFrom(this)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + return NS_OK; +} + +nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating) { + // Per spec, most load attempts are silently ignored when a BrowsingContext is + // null (which in our code corresponds to discarded), so we simply fail + // silently in those cases. Regardless, we cannot trigger loads in/from + // discarded BrowsingContexts via IPC, so we need to abort in any case. + if (IsDiscarded()) { + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "Targeting occurs in InternalLoad"); + + if (mDocShell) { + nsCOMPtr docShell = mDocShell; + return docShell->LoadURI(aLoadState, aSetNavigating); + } + + // Note: We do this check both here and in `nsDocShell::InternalLoad`, since + // document-specific sandbox flags are only available in the process + // triggering the load, and we don't want the target process to have to trust + // the triggering process to do the appropriate checks for the + // BrowsingContext's sandbox flags. + MOZ_TRY(CheckSandboxFlags(aLoadState)); + SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + + if (net::SchemeIsJavascript(aLoadState->URI())) { + if (!XRE_IsParentProcess()) { + // Web content should only be able to load javascript: URIs into documents + // whose principals the caller principal subsumes, which by definition + // excludes any document in a cross-process BrowsingContext. + return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; + } + MOZ_DIAGNOSTIC_ASSERT(!sourceBC, + "Should never see a cross-process javascript: load " + "triggered from content"); + } + + MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group()); + if (sourceBC && sourceBC->IsInProcess()) { + nsCOMPtr win(sourceBC->GetDOMWindow()); + if (WindowGlobalChild* wgc = + win->GetCurrentInnerWindow()->GetWindowGlobalChild()) { + if (!wgc->CanNavigate(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + wgc->SendLoadURI(this, mozilla::WrapNotNull(aLoadState), aSetNavigating); + } + } else if (XRE_IsParentProcess()) { + if (Canonical()->LoadInParent(aLoadState, aSetNavigating)) { + return NS_OK; + } + + if (ContentParent* cp = Canonical()->GetContentParent()) { + // Attempt to initiate this load immediately in the parent, if it succeeds + // it'll return a unique identifier so that we can find it later. + uint64_t loadIdentifier = 0; + if (Canonical()->AttemptSpeculativeLoadInParent(aLoadState)) { + MOZ_DIAGNOSTIC_ASSERT(GetCurrentLoadIdentifier().isSome()); + loadIdentifier = GetCurrentLoadIdentifier().value(); + aLoadState->SetChannelInitialized(true); + } + + cp->TransmitBlobDataIfBlobURL(aLoadState->URI()); + + // Setup a confirmation callback once the content process receives this + // load. Normally we'd expect a PDocumentChannel actor to have been + // created to claim the load identifier by that time. If not, then it + // won't be coming, so make sure we clean up and deregister. + cp->SendLoadURI(this, mozilla::WrapNotNull(aLoadState), aSetNavigating) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [loadIdentifier]( + const PContentParent::LoadURIPromise::ResolveOrRejectValue& + aValue) { + if (loadIdentifier) { + net::DocumentLoadListener::CleanupParentLoadAttempt( + loadIdentifier); + } + }); + } + } else { + MOZ_DIAGNOSTIC_ASSERT(sourceBC); + if (!sourceBC) { + return NS_ERROR_UNEXPECTED; + } + // If we're in a content process and the source BC is no longer in-process, + // just fail silently. + } + return NS_OK; +} + +nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) { + if (IsDiscarded()) { + return NS_OK; + } + SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + + MOZ_DIAGNOSTIC_ASSERT(aLoadState->Target().IsEmpty(), + "should already have retargeted"); + MOZ_DIAGNOSTIC_ASSERT(!aLoadState->TargetBrowsingContext().IsNull(), + "should have target bc set"); + MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext() == this, + "must be targeting this BrowsingContext"); + + if (mDocShell) { + RefPtr docShell = nsDocShell::Cast(mDocShell); + return docShell->InternalLoad(aLoadState); + } + + // Note: We do this check both here and in `nsDocShell::InternalLoad`, since + // document-specific sandbox flags are only available in the process + // triggering the load, and we don't want the target process to have to trust + // the triggering process to do the appropriate checks for the + // BrowsingContext's sandbox flags. + MOZ_TRY(CheckSandboxFlags(aLoadState)); + + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + + if (net::SchemeIsJavascript(aLoadState->URI())) { + if (!XRE_IsParentProcess()) { + // Web content should only be able to load javascript: URIs into documents + // whose principals the caller principal subsumes, which by definition + // excludes any document in a cross-process BrowsingContext. + return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; + } + MOZ_DIAGNOSTIC_ASSERT(!sourceBC, + "Should never see a cross-process javascript: load " + "triggered from content"); + } + + if (XRE_IsParentProcess()) { + ContentParent* cp = Canonical()->GetContentParent(); + if (!cp || !cp->CanSend()) { + return NS_ERROR_FAILURE; + } + + MOZ_ALWAYS_SUCCEEDS( + SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); + Unused << cp->SendInternalLoad(mozilla::WrapNotNull(aLoadState)); + } else { + MOZ_DIAGNOSTIC_ASSERT(sourceBC); + MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group()); + + nsCOMPtr win(sourceBC->GetDOMWindow()); + WindowGlobalChild* wgc = + win->GetCurrentInnerWindow()->GetWindowGlobalChild(); + if (!wgc || !wgc->CanSend()) { + return NS_ERROR_FAILURE; + } + if (!wgc->CanNavigate(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + MOZ_ALWAYS_SUCCEEDS( + SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); + wgc->SendInternalLoad(mozilla::WrapNotNull(aLoadState)); + } + + return NS_OK; +} + +void BrowsingContext::DisplayLoadError(const nsAString& aURI) { + MOZ_LOG(GetLog(), LogLevel::Debug, ("DisplayLoadError")); + MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded()); + MOZ_DIAGNOSTIC_ASSERT(mDocShell || XRE_IsParentProcess()); + + if (mDocShell) { + bool didDisplayLoadError = false; + nsCOMPtr docShell = mDocShell; + docShell->DisplayLoadError(NS_ERROR_MALFORMED_URI, nullptr, + PromiseFlatString(aURI).get(), nullptr, + &didDisplayLoadError); + } else { + if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendDisplayLoadError(this, PromiseFlatString(aURI)); + } + } +} + +WindowProxyHolder BrowsingContext::Window() { + return WindowProxyHolder(Self()); +} + +WindowProxyHolder BrowsingContext::GetFrames(ErrorResult& aError) { + return Window(); +} + +void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) { + if (mIsDiscarded) { + return; + } + + if (IsSubframe()) { + // .close() on frames is a no-op. + return; + } + + if (GetDOMWindow()) { + nsGlobalWindowOuter::Cast(GetDOMWindow()) + ->CloseOuter(aCallerType == CallerType::System); + return; + } + + // This is a bit of a hack for webcompat. Content needs to see an updated + // |window.closed| value as early as possible, so we set this before we + // actually send the DOMWindowClose event, which happens in the process where + // the document for this browsing context is loaded. + MOZ_ALWAYS_SUCCEEDS(SetClosed(true)); + + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowClose(this, aCallerType == CallerType::System); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowClose(this, aCallerType == CallerType::System); + } +} + +/* + * Examine the current document state to see if we're in a way that is + * typically abused by web designers. The window.open code uses this + * routine to determine whether to allow the new window. + * Returns a value from the PopupControlState enum. + */ +PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel( + PopupBlocker::PopupControlState aControl) { + if (!IsContent()) { + return PopupBlocker::openAllowed; + } + + RefPtr doc = GetExtantDocument(); + PopupBlocker::PopupControlState abuse = aControl; + switch (abuse) { + case PopupBlocker::openControlled: + case PopupBlocker::openBlocked: + case PopupBlocker::openOverridden: + if (IsPopupAllowed()) { + abuse = PopupBlocker::PopupControlState(abuse - 1); + } + break; + case PopupBlocker::openAbused: + if (IsPopupAllowed() || + (doc && doc->HasValidTransientUserGestureActivation())) { + // Skip PopupBlocker::openBlocked + abuse = PopupBlocker::openControlled; + } + break; + case PopupBlocker::openAllowed: + break; + default: + NS_WARNING("Strange PopupControlState!"); + } + + // limit the number of simultaneously open popups + if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked || + abuse == PopupBlocker::openControlled) { + int32_t popupMax = StaticPrefs::dom_popup_maximum(); + if (popupMax >= 0 && + PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) { + abuse = PopupBlocker::openOverridden; + } + } + + // If we're currently in-process, attempt to consume transient user gesture + // activations. + if (doc) { + // HACK: Some pages using bogus library + UA sniffing call window.open() + // from a blank iframe, only on Firefox, see bug 1685056. + // + // This is a hack-around to preserve behavior in that particular and + // specific case, by consuming activation on the parent document, so we + // don't care about the InProcessParent bits not being fission-safe or what + // not. + auto ConsumeTransientUserActivationForMultiplePopupBlocking = + [&]() -> bool { + if (doc->ConsumeTransientUserGestureActivation()) { + return true; + } + if (!doc->IsInitialDocument()) { + return false; + } + Document* parentDoc = doc->GetInProcessParentDocument(); + if (!parentDoc || + !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) { + return false; + } + return parentDoc->ConsumeTransientUserGestureActivation(); + }; + + // If this popup is allowed, let's block any other for this event, forcing + // PopupBlocker::openBlocked state. + if ((abuse == PopupBlocker::openAllowed || + abuse == PopupBlocker::openControlled) && + StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() && + !ConsumeTransientUserActivationForMultiplePopupBlocking()) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, + doc, nsContentUtils::eDOM_PROPERTIES, + "MultiplePopupsBlockedNoUserActivation"); + abuse = PopupBlocker::openBlocked; + } + } + + return abuse; +} + +void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() { + Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1); +} + +std::tuple BrowsingContext::CanFocusCheck(CallerType aCallerType) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return {false, false}; + } + + nsCOMPtr caller = do_QueryInterface(GetEntryGlobal()); + BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr; + RefPtr openerBC = GetOpener(); + MOZ_DIAGNOSTIC_ASSERT(!openerBC || openerBC->Group() == Group()); + + // Enforce dom.disable_window_flip (for non-chrome), but still allow the + // window which opened us to raise us at times when popups are allowed + // (bugs 355482 and 369306). + bool canFocus = aCallerType == CallerType::System || + !Preferences::GetBool("dom.disable_window_flip", true); + if (!canFocus && openerBC == callerBC) { + canFocus = + (callerBC ? callerBC : this) + ->RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) < + PopupBlocker::openBlocked; + } + + bool isActive = false; + if (XRE_IsParentProcess()) { + RefPtr chromeTop = + Canonical()->TopCrossChromeBoundary(); + nsCOMPtr activeWindow = fm->GetActiveWindow(); + isActive = (activeWindow == chromeTop->GetDOMWindow()); + } else { + isActive = (fm->GetActiveBrowsingContext() == Top()); + } + + return {canFocus, isActive}; +} + +void BrowsingContext::Focus(CallerType aCallerType, ErrorResult& aError) { + // These checks need to happen before the RequestFrameFocus call, which + // is why they are done in an untrusted process. If we wanted to enforce + // these in the parent, we'd need to do the checks there _also_. + // These should be kept in sync with nsGlobalWindowOuter::FocusOuter. + + auto [canFocus, isActive] = CanFocusCheck(aCallerType); + + if (!(canFocus || isActive)) { + return; + } + + // Permission check passed + + if (mEmbedderElement) { + // Make the activeElement in this process update synchronously. + nsContentUtils::RequestFrameFocus(*mEmbedderElement, true, aCallerType); + } + uint64_t actionId = nsFocusManager::GenerateFocusActionId(); + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowFocus(this, aCallerType, actionId); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowFocus(this, aCallerType, actionId); + } +} + +bool BrowsingContext::CanBlurCheck(CallerType aCallerType) { + // If dom.disable_window_flip == true, then content should not be allowed + // to do blur (this would allow popunders, bug 369306) + return aCallerType == CallerType::System || + !Preferences::GetBool("dom.disable_window_flip", true); +} + +void BrowsingContext::Blur(CallerType aCallerType, ErrorResult& aError) { + if (!CanBlurCheck(aCallerType)) { + return; + } + + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowBlur(this, aCallerType); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowBlur(this, aCallerType); + } +} + +Nullable BrowsingContext::GetWindow() { + if (XRE_IsParentProcess() && !IsInProcess()) { + return nullptr; + } + return WindowProxyHolder(this); +} + +Nullable BrowsingContext::GetTop(ErrorResult& aError) { + if (mIsDiscarded) { + return nullptr; + } + + // We never return null or throw an error, but the implementation in + // nsGlobalWindow does and we need to use the same signature. + return WindowProxyHolder(Top()); +} + +void BrowsingContext::GetOpener(JSContext* aCx, + JS::MutableHandle aOpener, + ErrorResult& aError) const { + RefPtr opener = GetOpener(); + if (!opener) { + aOpener.setNull(); + return; + } + + if (!ToJSValue(aCx, WindowProxyHolder(opener), aOpener)) { + aError.NoteJSContextException(aCx); + } +} + +// We never throw an error, but the implementation in nsGlobalWindow does and +// we need to use the same signature. +Nullable BrowsingContext::GetParent(ErrorResult& aError) { + if (mIsDiscarded) { + return nullptr; + } + + if (GetParent()) { + return WindowProxyHolder(GetParent()); + } + return WindowProxyHolder(this); +} + +void BrowsingContext::PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, + const nsAString& aTargetOrigin, + const Sequence& aTransfer, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) { + if (mIsDiscarded) { + return; + } + + RefPtr sourceBc; + PostMessageData data; + data.targetOrigin() = aTargetOrigin; + data.subjectPrincipal() = &aSubjectPrincipal; + RefPtr callerInnerWindow; + nsAutoCString scriptLocation; + // We don't need to get the caller's agentClusterId since that is used for + // checking whether it's okay to sharing memory (and it's not allowed to share + // memory cross processes) + if (!nsGlobalWindowOuter::GatherPostMessageData( + aCx, aTargetOrigin, getter_AddRefs(sourceBc), data.origin(), + getter_AddRefs(data.targetOriginURI()), + getter_AddRefs(data.callerPrincipal()), + getter_AddRefs(callerInnerWindow), getter_AddRefs(data.callerURI()), + /* aCallerAgentClusterId */ nullptr, &scriptLocation, aError)) { + return; + } + if (sourceBc && sourceBc->IsDiscarded()) { + return; + } + data.source() = sourceBc; + data.isFromPrivateWindow() = + callerInnerWindow && + nsScriptErrorBase::ComputeIsFromPrivateWindow(callerInnerWindow); + data.innerWindowId() = callerInnerWindow ? callerInnerWindow->WindowID() : 0; + data.scriptLocation() = scriptLocation; + JS::Rooted transferArray(aCx); + aError = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer, + &transferArray); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + JS::CloneDataPolicy clonePolicy; + if (callerInnerWindow && callerInnerWindow->IsSharedMemoryAllowed()) { + clonePolicy.allowSharedMemoryObjects(); + } + + // We will see if the message is required to be in the same process or it can + // be in the different process after Write(). + ipc::StructuredCloneData message = ipc::StructuredCloneData( + StructuredCloneHolder::StructuredCloneScope::UnknownDestination, + StructuredCloneHolder::TransferringSupported); + message.Write(aCx, aMessage, transferArray, clonePolicy, aError); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + ClonedOrErrorMessageData messageData; + if (ContentChild* cc = ContentChild::GetSingleton()) { + // The clone scope gets set when we write the message data based on the + // requirements of that data that we're writing. + // If the message data contains a shared memory object, then CloneScope + // would return SameProcess. Otherwise, it returns DifferentProcess. + if (message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { + ClonedMessageData clonedMessageData; + if (!message.BuildClonedMessageData(clonedMessageData)) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + messageData = std::move(clonedMessageData); + } else { + MOZ_ASSERT(message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess); + + messageData = ErrorMessageData(); + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM Window"_ns, + callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "PostMessageSharedMemoryObjectToCrossOriginWarning"); + } + + cc->SendWindowPostMessage(this, messageData, data); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + if (message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { + ClonedMessageData clonedMessageData; + if (!message.BuildClonedMessageData(clonedMessageData)) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + messageData = std::move(clonedMessageData); + } else { + MOZ_ASSERT(message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess); + + messageData = ErrorMessageData(); + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM Window"_ns, + callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "PostMessageSharedMemoryObjectToCrossOriginWarning"); + } + + Unused << cp->SendWindowPostMessage(this, messageData, data); + } +} + +void BrowsingContext::PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, + const WindowPostMessageOptions& aOptions, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) { + PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer, + aSubjectPrincipal, aError); +} + +void BrowsingContext::SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + Unused << aParent->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch); +} + +void BrowsingContext::SendCommitTransaction(ContentChild* aChild, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + aChild->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch); +} + +BrowsingContext::IPCInitializer BrowsingContext::GetIPCInitializer() { + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_DIAGNOSTIC_ASSERT(mType == Type::Content); + + IPCInitializer init; + init.mId = Id(); + init.mParentId = mParentWindow ? mParentWindow->Id() : 0; + init.mWindowless = mWindowless; + init.mUseRemoteTabs = mUseRemoteTabs; + init.mUseRemoteSubframes = mUseRemoteSubframes; + init.mCreatedDynamically = mCreatedDynamically; + init.mChildOffset = mChildOffset; + init.mOriginAttributes = mOriginAttributes; + if (mChildSessionHistory && mozilla::SessionHistoryInParent()) { + init.mSessionHistoryIndex = mChildSessionHistory->Index(); + init.mSessionHistoryCount = mChildSessionHistory->Count(); + } + init.mRequestContextId = mRequestContextId; + init.mFields = mFields.RawValues(); + return init; +} + +already_AddRefed BrowsingContext::IPCInitializer::GetParent() { + RefPtr parent; + if (mParentId != 0) { + parent = WindowContext::GetById(mParentId); + MOZ_RELEASE_ASSERT(parent); + } + return parent.forget(); +} + +already_AddRefed BrowsingContext::IPCInitializer::GetOpener() { + RefPtr opener; + if (GetOpenerId() != 0) { + opener = BrowsingContext::Get(GetOpenerId()); + MOZ_RELEASE_ASSERT(opener); + } + return opener.forget(); +} + +void BrowsingContext::StartDelayedAutoplayMediaComponents() { + if (!mDocShell) { + return; + } + AUTOPLAY_LOG("%s : StartDelayedAutoplayMediaComponents for bc 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + mDocShell->StartDelayedAutoplayMediaComponents(); +} + +nsresult BrowsingContext::ResetGVAutoplayRequestStatus() { + MOZ_ASSERT(IsTop(), + "Should only set GVAudibleAutoplayRequestStatus in the top-level " + "browsing context"); + + Transaction txn; + txn.SetGVAudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN); + txn.SetGVInaudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN); + return txn.Commit(this); +} + +template +void BrowsingContext::WalkPresContexts(Callback&& aCallback) { + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsIDocShell* shell = aContext->GetDocShell()) { + if (RefPtr pc = shell->GetPresContext()) { + aCallback(pc.get()); + } + } + }); +} + +void BrowsingContext::PresContextAffectingFieldChanged() { + WalkPresContexts([&](nsPresContext* aPc) { + aPc->RecomputeBrowsingContextDependentData(); + }); +} + +void BrowsingContext::DidSet(FieldIndex, + uint32_t aOldValue) { + if (!mCurrentWindowContext) { + return; + } + SessionStoreChild* sessionStoreChild = + SessionStoreChild::From(mCurrentWindowContext->GetWindowGlobalChild()); + if (!sessionStoreChild) { + return; + } + + sessionStoreChild->SetEpoch(GetSessionStoreEpoch()); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), + "Should only set GVAudibleAutoplayRequestStatus in the top-level " + "browsing context"); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), + "Should only set GVAudibleAutoplayRequestStatus in the top-level " + "browsing context"); +} + +void BrowsingContext::DidSet(FieldIndex, + ExplicitActiveStatus aOldValue) { + const bool isActive = IsActive(); + const bool wasActive = [&] { + if (aOldValue != ExplicitActiveStatus::None) { + return aOldValue == ExplicitActiveStatus::Active; + } + return GetParent() && GetParent()->IsActive(); + }(); + + if (isActive == wasActive) { + return; + } + + if (IsTop()) { + Group()->UpdateToplevelsSuspendedIfNeeded(); + + if (XRE_IsParentProcess()) { + auto* bc = Canonical(); + if (BrowserParent* bp = bc->GetBrowserParent()) { + bp->RecomputeProcessPriority(); +#if defined(XP_WIN) && defined(ACCESSIBILITY) + if (a11y::Compatibility::IsDolphin()) { + // update active accessible documents on windows + if (a11y::DocAccessibleParent* tabDoc = + bp->GetTopLevelDocAccessible()) { + HWND window = tabDoc->GetEmulatedWindowHandle(); + MOZ_ASSERT(window); + if (window) { + if (isActive) { + a11y::nsWinUtils::ShowNativeWindow(window); + } else { + a11y::nsWinUtils::HideNativeWindow(window); + } + } + } + } +#endif + } + } + } + + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsCOMPtr ds = aContext->GetDocShell()) { + nsDocShell::Cast(ds)->ActivenessMaybeChanged(); + } + }); +} + +void BrowsingContext::DidSet(FieldIndex, bool aOldValue) { + MOZ_ASSERT(IsTop(), + "Should only set InRDMPane in the top-level browsing context"); + if (GetInRDMPane() == aOldValue) { + return; + } + PresContextAffectingFieldChanged(); +} + +bool BrowsingContext::CanSet(FieldIndex, + uint32_t aNewValue, ContentParent* aSource) { + return IsTop() && XRE_IsParentProcess() && !aSource; +} + +void BrowsingContext::DidSet(FieldIndex, + uint32_t aOldValue) { + if (!IsTop() || aOldValue == GetPageAwakeRequestCount()) { + return; + } + Group()->UpdateToplevelsSuspendedIfNeeded(); +} + +auto BrowsingContext::CanSet(FieldIndex, bool aValue, + ContentParent* aSource) -> CanSetResult { + if (mozilla::SessionHistoryInParent()) { + return XRE_IsParentProcess() && !aSource ? CanSetResult::Allow + : CanSetResult::Deny; + } + + // Without Session History in Parent, session restore code still needs to set + // this from content processes. + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +void BrowsingContext::DidSet(FieldIndex, bool aOldValue) { + RecomputeCanExecuteScripts(); +} + +void BrowsingContext::RecomputeCanExecuteScripts() { + const bool old = mCanExecuteScripts; + if (!AllowJavascript()) { + // Scripting has been explicitly disabled on our BrowsingContext. + mCanExecuteScripts = false; + } else if (GetParentWindowContext()) { + // Otherwise, inherit parent. + mCanExecuteScripts = GetParentWindowContext()->CanExecuteScripts(); + } else { + // Otherwise, we're the root of the tree, and we haven't explicitly disabled + // script. Allow. + mCanExecuteScripts = true; + } + + if (old != mCanExecuteScripts) { + for (WindowContext* wc : GetWindowContexts()) { + wc->RecomputeCanExecuteScripts(); + } + } +} + +bool BrowsingContext::InactiveForSuspend() const { + if (!StaticPrefs::dom_suspend_inactive_enabled()) { + return false; + } + // We should suspend a page only when it's inactive and doesn't have any awake + // request that is used to prevent page from being suspended because web page + // might still need to run their script. Eg. waiting for media keys to resume + // media, playing web audio, waiting in a video call conference room. + return !IsActive() && GetPageAwakeRequestCount() == 0; +} + +bool BrowsingContext::CanSet(FieldIndex, + dom::TouchEventsOverride, ContentParent* aSource) { + return XRE_IsParentProcess() && !aSource; +} + +void BrowsingContext::DidSet(FieldIndex, + dom::TouchEventsOverride&& aOldValue) { + if (GetTouchEventsOverrideInternal() == aOldValue) { + return; + } + WalkPresContexts([&](nsPresContext* aPc) { + aPc->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::SystemMetricsChange}, + // We're already iterating through sub documents, so we don't need to + // propagate the change again. + MediaFeatureChangePropagation::JustThisDocument); + }); +} + +void BrowsingContext::DidSet(FieldIndex, + EmbedderColorSchemes&& aOldValue) { + if (GetEmbedderColorSchemes() == aOldValue) { + return; + } + PresContextAffectingFieldChanged(); +} + +void BrowsingContext::DidSet(FieldIndex, + dom::PrefersColorSchemeOverride aOldValue) { + MOZ_ASSERT(IsTop()); + if (PrefersColorSchemeOverride() == aOldValue) { + return; + } + PresContextAffectingFieldChanged(); +} + +void BrowsingContext::DidSet(FieldIndex, + nsString&& aOldValue) { + MOZ_ASSERT(IsTop()); + if (GetMediumOverride() == aOldValue) { + return; + } + PresContextAffectingFieldChanged(); +} + +void BrowsingContext::DidSet(FieldIndex, + enum DisplayMode aOldValue) { + MOZ_ASSERT(IsTop()); + + if (GetDisplayMode() == aOldValue) { + return; + } + + WalkPresContexts([&](nsPresContext* aPc) { + aPc->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::DisplayModeChange}, + // We're already iterating through sub documents, so we don't need + // to propagate the change again. + // + // Images and other resources don't change their display-mode + // evaluation, display-mode is a property of the browsing context. + MediaFeatureChangePropagation::JustThisDocument); + }); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), "Set muted flag on non top-level context!"); + USER_ACTIVATION_LOG("Set audio muted %d for %s browsing context 0x%08" PRIx64, + GetMuted(), XRE_IsParentProcess() ? "Parent" : "Child", + Id()); + PreOrderWalk([&](BrowsingContext* aContext) { + nsPIDOMWindowOuter* win = aContext->GetDOMWindow(); + if (win) { + win->RefreshMediaElementsVolume(); + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return XRE_IsParentProcess() && !aSource && IsTop(); +} + +bool BrowsingContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return XRE_IsParentProcess() && !aSource && IsTop(); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aValue, ContentParent* aSource) { + return IsTop(); +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + MOZ_ASSERT(IsTop(), "Set attribute on non top-level context!"); + if (aOldValue == GetShouldDelayMediaFromStart()) { + return; + } + if (!GetShouldDelayMediaFromStart()) { + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsPIDOMWindowOuter* win = aContext->GetDOMWindow()) { + win->ActivateMediaComponents(); + } + }); + } +} + +bool BrowsingContext::CanSet(FieldIndex, const float& aValue, + ContentParent* aSource) { + return XRE_IsParentProcess() && !aSource && IsTop(); +} + +void BrowsingContext::DidSet(FieldIndex, float aOldValue) { + MOZ_ASSERT(IsTop()); + if (GetOverrideDPPX() == aOldValue) { + return; + } + PresContextAffectingFieldChanged(); +} + +void BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent, + ErrorResult& aRv) { + Top()->SetUserAgentOverride(aUserAgent, aRv); +} + +nsresult BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent) { + return Top()->SetUserAgentOverride(aUserAgent); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop()); + + PreOrderWalk([&](BrowsingContext* aContext) { + nsIDocShell* shell = aContext->GetDocShell(); + if (shell) { + shell->ClearCachedUserAgent(); + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex, bool, + ContentParent* aSource) { + return IsTop() && !aSource && mozilla::BFCacheInParent(); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_RELEASE_ASSERT(mozilla::BFCacheInParent()); + MOZ_DIAGNOSTIC_ASSERT(IsTop()); + + const bool isInBFCache = GetIsInBFCache(); + if (!isInBFCache) { + UpdateCurrentTopByBrowserId(this); + PreOrderWalk([&](BrowsingContext* aContext) { + aContext->mIsInBFCache = false; + nsCOMPtr shell = aContext->GetDocShell(); + if (shell) { + nsDocShell::Cast(shell)->ThawFreezeNonRecursive(true); + } + }); + } + + if (isInBFCache && XRE_IsContentProcess() && mDocShell) { + nsDocShell::Cast(mDocShell)->MaybeDisconnectChildListenersOnPageHide(); + } + + if (isInBFCache) { + PreOrderWalk([&](BrowsingContext* aContext) { + nsCOMPtr shell = aContext->GetDocShell(); + if (shell) { + nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(false); + } + }); + } else { + PostOrderWalk([&](BrowsingContext* aContext) { + nsCOMPtr shell = aContext->GetDocShell(); + if (shell) { + nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(true); + } + }); + } + + if (isInBFCache) { + PreOrderWalk([&](BrowsingContext* aContext) { + nsCOMPtr shell = aContext->GetDocShell(); + if (shell) { + nsDocShell::Cast(shell)->ThawFreezeNonRecursive(false); + if (nsPresContext* pc = shell->GetPresContext()) { + pc->EventStateManager()->ResetHoverState(); + } + } + aContext->mIsInBFCache = true; + Document* doc = aContext->GetDocument(); + if (doc) { + // Notifying needs to happen after mIsInBFCache is set to true. + doc->NotifyActivityChanged(); + } + }); + + if (XRE_IsParentProcess()) { + if (mCurrentWindowContext && + mCurrentWindowContext->Canonical()->Fullscreen()) { + mCurrentWindowContext->Canonical()->ExitTopChromeDocumentFullscreen(); + } + } + } +} + +void BrowsingContext::DidSet(FieldIndex) { + if (WindowContext* parentWindowContext = GetParentWindowContext()) { + parentWindowContext->UpdateChildSynthetic(this, + GetSyntheticDocumentContainer()); + } +} + +void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform, + ErrorResult& aRv) { + Top()->SetPlatformOverride(aPlatform, aRv); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop()); + + PreOrderWalk([&](BrowsingContext* aContext) { + nsIDocShell* shell = aContext->GetDocShell(); + if (shell) { + shell->ClearCachedPlatform(); + } + }); +} + +auto BrowsingContext::LegacyRevertIfNotOwningOrParentProcess( + ContentParent* aSource) -> CanSetResult { + if (aSource) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) { + return CanSetResult::Revert; + } + } else if (!IsInProcess() && !XRE_IsParentProcess()) { + // Don't allow this to be set from content processes that + // don't own the BrowsingContext. + return CanSetResult::Deny; + } + + return CanSetResult::Allow; +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aValue, ContentParent* aSource) { + // Should only be set in the parent process. + return XRE_IsParentProcess() && !aSource && IsTop(); +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + bool isActivateEvent = GetIsActiveBrowserWindowInternal(); + // The browser window containing this context has changed + // activation state so update window inactive document states + // for all in-process documents. + PreOrderWalk([isActivateEvent](BrowsingContext* aContext) { + if (RefPtr doc = aContext->GetExtantDocument()) { + doc->UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, true); + + RefPtr win = doc->GetInnerWindow(); + if (win) { + RefPtr devices; + if (isActivateEvent && (devices = win->GetExtantMediaDevices())) { + devices->BrowserWindowBecameActive(); + } + + if (XRE_IsContentProcess() && + (!aContext->GetParent() || !aContext->GetParent()->IsInProcess())) { + // Send the inner window an activate/deactivate event if + // the context is the top of a sub-tree of in-process + // contexts. + nsContentUtils::DispatchEventOnlyToChrome( + doc, win, isActivateEvent ? u"activate"_ns : u"deactivate"_ns, + CanBubble::eYes, Cancelable::eYes, nullptr); + } + } + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex, + nsILoadInfo::CrossOriginOpenerPolicy aPolicy, + ContentParent* aSource) { + // A potentially cross-origin isolated BC can't change opener policy, nor can + // a BC become potentially cross-origin isolated. An unchanged policy is + // always OK. + return GetOpenerPolicy() == aPolicy || + (GetOpenerPolicy() != + nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP && + aPolicy != + nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP); +} + +auto BrowsingContext::CanSet(FieldIndex, + const bool& aAllowContentRetargeting, + ContentParent* aSource) -> CanSetResult { + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +auto BrowsingContext::CanSet(FieldIndex, + const bool& aAllowContentRetargetingOnChildren, + ContentParent* aSource) -> CanSetResult { + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +auto BrowsingContext::CanSet(FieldIndex, + const bool& aAllowPlugins, ContentParent* aSource) + -> CanSetResult { + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aAllowed, ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aUseErrorPages, + ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); +} + +TouchEventsOverride BrowsingContext::TouchEventsOverride() const { + for (const auto* bc = this; bc; bc = bc->GetParent()) { + auto tev = bc->GetTouchEventsOverrideInternal(); + if (tev != dom::TouchEventsOverride::None) { + return tev; + } + } + return dom::TouchEventsOverride::None; +} + +bool BrowsingContext::TargetTopLevelLinkClicksToBlank() const { + return Top()->GetTargetTopLevelLinkClicksToBlankInternal(); +} + +// We map `watchedByDevTools` WebIDL attribute to `watchedByDevToolsInternal` +// BC field. And we map it to the top level BrowsingContext. +bool BrowsingContext::WatchedByDevTools() { + return Top()->GetWatchedByDevToolsInternal(); +} + +// Enforce that the watchedByDevTools BC field can only be set on the top level +// Browsing Context. +bool BrowsingContext::CanSet(FieldIndex, + const bool& aWatchedByDevTools, + ContentParent* aSource) { + return IsTop(); +} +void BrowsingContext::SetWatchedByDevTools(bool aWatchedByDevTools, + ErrorResult& aRv) { + if (!IsTop()) { + aRv.ThrowInvalidModificationError( + "watchedByDevTools can only be set on top BrowsingContext"); + return; + } + SetWatchedByDevToolsInternal(aWatchedByDevTools, aRv); +} + +auto BrowsingContext::CanSet(FieldIndex, + const uint32_t& aDefaultLoadFlags, + ContentParent* aSource) -> CanSetResult { + // Bug 1623565 - Are these flags only used by the debugger, which makes it + // possible that this field can only be settable by the parent process? + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +void BrowsingContext::DidSet(FieldIndex) { + auto loadFlags = GetDefaultLoadFlags(); + if (GetDocShell()) { + nsDocShell::Cast(GetDocShell())->SetLoadGroupDefaultLoadFlags(loadFlags); + } + + if (XRE_IsParentProcess()) { + PreOrderWalk([&](BrowsingContext* aContext) { + if (aContext != this) { + // Setting load flags on a discarded context has no effect. + Unused << aContext->SetDefaultLoadFlags(loadFlags); + } + }); + } +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aUseGlobalHistory, + ContentParent* aSource) { + // Should only be set in the parent process. + // return XRE_IsParentProcess() && !aSource; + return true; +} + +auto BrowsingContext::CanSet(FieldIndex, + const nsString& aUserAgent, ContentParent* aSource) + -> CanSetResult { + if (!IsTop()) { + return CanSetResult::Deny; + } + + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +auto BrowsingContext::CanSet(FieldIndex, + const nsString& aPlatform, ContentParent* aSource) + -> CanSetResult { + if (!IsTop()) { + return CanSetResult::Deny; + } + + return LegacyRevertIfNotOwningOrParentProcess(aSource); +} + +bool BrowsingContext::CheckOnlyEmbedderCanSet(ContentParent* aSource) { + if (XRE_IsParentProcess()) { + uint64_t childId = aSource ? aSource->ChildID() : 0; + return Canonical()->IsEmbeddedInProcess(childId); + } + return mEmbeddedByThisProcess; +} + +bool BrowsingContext::CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource) { + // If we have a parent window, our embedder inner window ID must match it. + if (mParentWindow) { + return mParentWindow->Id() == aValue; + } + + // For toplevel BrowsingContext instances, this value may only be set by the + // parent process, or initialized to `0`. + return CheckOnlyEmbedderCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const Maybe&, ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); +} + +auto BrowsingContext::CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource) + -> CanSetResult { + // Generally allow clearing this. We may want to be more precise about this + // check in the future. + if (aValue == 0) { + return CanSetResult::Allow; + } + + // We must have access to the specified context. + RefPtr window = WindowContext::GetById(aValue); + if (!window || window->GetBrowsingContext() != this) { + return CanSetResult::Deny; + } + + if (aSource) { + // If the sending process is no longer the current owner, revert + MOZ_ASSERT(XRE_IsParentProcess()); + if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) { + return CanSetResult::Revert; + } + } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) { + return CanSetResult::Deny; + } + + return CanSetResult::Allow; +} + +bool BrowsingContext::CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource) { + return XRE_IsParentProcess() && !aSource; +} + +void BrowsingContext::DidSet(FieldIndex) { + RefPtr prevWindowContext = mCurrentWindowContext.forget(); + mCurrentWindowContext = WindowContext::GetById(GetCurrentInnerWindowId()); + MOZ_ASSERT( + !mCurrentWindowContext || mWindowContexts.Contains(mCurrentWindowContext), + "WindowContext not registered?"); + + // Clear our cached `children` value, to ensure that JS sees the up-to-date + // value. + BrowsingContext_Binding::ClearCachedChildrenValue(this); + + if (XRE_IsParentProcess()) { + if (prevWindowContext != mCurrentWindowContext) { + if (prevWindowContext) { + prevWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(false); + } + if (mCurrentWindowContext) { + // We set a timer when we set the current inner window. This + // will then flush the session storage to session store to + // make sure that we don't miss to store session storage to + // session store that is a result of navigation. This is due + // to Bug 1700623. We wish to fix this in Bug 1711886, where + // making sure to store everything would make this timer + // unnecessary. + Canonical()->MaybeScheduleSessionStoreUpdate(); + mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true); + } + } + BrowserParent::UpdateFocusFromBrowsingContext(); + } +} + +bool BrowsingContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + // Ensure that we only mark a browsing context as popup spam once and never + // unmark it. + return aValue && !GetIsPopupSpam(); +} + +void BrowsingContext::DidSet(FieldIndex) { + if (GetIsPopupSpam()) { + PopupBlocker::RegisterOpenPopupSpam(); + } +} + +bool BrowsingContext::CanSet(FieldIndex, + const nsString& aMessageManagerGroup, + ContentParent* aSource) { + // Should only be set in the parent process on toplevel. + return XRE_IsParentProcess() && !aSource && IsTopContent(); +} + +bool BrowsingContext::CanSet( + FieldIndex, + const mozilla::hal::ScreenOrientation& aOrientationLock, + ContentParent* aSource) { + return IsTop(); +} + +bool BrowsingContext::IsLoading() { + if (GetLoading()) { + return true; + } + + // If we're in the same process as the page, we're possibly just + // updating the flag. + nsIDocShell* shell = GetDocShell(); + if (shell) { + Document* doc = shell->GetDocument(); + return doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE; + } + + return false; +} + +void BrowsingContext::DidSet(FieldIndex) { + if (mFields.Get()) { + return; + } + + while (!mDeprioritizedLoadRunner.isEmpty()) { + nsCOMPtr runner = mDeprioritizedLoadRunner.popFirst(); + NS_DispatchToCurrentThread(runner.forget()); + } + + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled() && + Top() == this) { + Group()->FlushPostMessageEvents(); + } +} + +// Inform the Document for this context of the (potential) change in +// loading state +void BrowsingContext::DidSet(FieldIndex) { + nsPIDOMWindowOuter* outer = GetDOMWindow(); + if (!outer) { + MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, + ("DidSetAncestorLoading BC: %p -- No outer window", (void*)this)); + return; + } + Document* document = nsGlobalWindowOuter::Cast(outer)->GetExtantDoc(); + if (document) { + MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, + ("DidSetAncestorLoading BC: %p -- NotifyLoading(%d, %d, %d)", + (void*)this, GetAncestorLoading(), document->GetReadyStateEnum(), + document->GetReadyStateEnum())); + document->NotifyLoading(GetAncestorLoading(), document->GetReadyStateEnum(), + document->GetReadyStateEnum()); + } +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), + "Should only set AuthorStyleDisabledDefault in the top " + "browsing context"); + + // We don't need to handle changes to this field, since PageStyleChild.sys.mjs + // will respond to the PageStyle:Disable message in all content processes. + // + // But we store the state here on the top BrowsingContext so that the + // docshell has somewhere to look for the current author style disabling + // state when new iframes are inserted. +} + +void BrowsingContext::DidSet(FieldIndex, float aOldValue) { + if (GetTextZoom() == aOldValue) { + return; + } + + if (IsInProcess()) { + if (nsIDocShell* shell = GetDocShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->RecomputeBrowsingContextDependentData(); + } + } + + for (BrowsingContext* child : Children()) { + // Setting text zoom on a discarded context has no effect. + Unused << child->SetTextZoom(GetTextZoom()); + } + } + + if (IsTop() && XRE_IsParentProcess()) { + if (Element* element = GetEmbedderElement()) { + AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"TextZoomChange"_ns, + CanBubble::eYes, + ChromeOnlyDispatch::eYes); + } + } +} + +// TODO(emilio): It'd be potentially nicer and cheaper to allow to set this only +// on the Top() browsing context, but there are a lot of tests that rely on +// zooming a subframe so... +void BrowsingContext::DidSet(FieldIndex, float aOldValue) { + if (GetFullZoom() == aOldValue) { + return; + } + + if (IsInProcess()) { + if (nsIDocShell* shell = GetDocShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->RecomputeBrowsingContextDependentData(); + } + } + + for (BrowsingContext* child : Children()) { + // Setting full zoom on a discarded context has no effect. + Unused << child->SetFullZoom(GetFullZoom()); + } + } + + if (IsTop() && XRE_IsParentProcess()) { + if (Element* element = GetEmbedderElement()) { + AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"FullZoomChange"_ns, + CanBubble::eYes, + ChromeOnlyDispatch::eYes); + } + } +} + +void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) { + MOZ_ASSERT(IsLoading()); + MOZ_ASSERT(Top() == this); + + RefPtr runner = new DeprioritizedLoadRunner(aRunner); + mDeprioritizedLoadRunner.insertBack(runner); + NS_DispatchToCurrentThreadQueue( + runner.forget(), StaticPrefs::page_load_deprioritization_period(), + EventQueuePriority::Idle); +} + +bool BrowsingContext::IsDynamic() const { + const BrowsingContext* current = this; + do { + if (current->CreatedDynamically()) { + return true; + } + } while ((current = current->GetParent())); + + return false; +} + +bool BrowsingContext::GetOffsetPath(nsTArray& aPath) const { + for (const BrowsingContext* current = this; current && current->GetParent(); + current = current->GetParent()) { + if (current->CreatedDynamically()) { + return false; + } + aPath.AppendElement(current->ChildOffset()); + } + return true; +} + +void BrowsingContext::GetHistoryID(JSContext* aCx, + JS::MutableHandle aVal, + ErrorResult& aError) { + if (!xpc::ID2JSValue(aCx, GetHistoryID(), aVal)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + } +} + +void BrowsingContext::InitSessionHistory() { + MOZ_ASSERT(!IsDiscarded()); + MOZ_ASSERT(IsTop()); + MOZ_ASSERT(EverAttached()); + + if (!GetHasSessionHistory()) { + MOZ_ALWAYS_SUCCEEDS(SetHasSessionHistory(true)); + } +} + +ChildSHistory* BrowsingContext::GetChildSessionHistory() { + if (!mozilla::SessionHistoryInParent()) { + // For now we're checking that the session history object for the child + // process is available before returning the ChildSHistory object, because + // it is the actual implementation that ChildSHistory forwards to. This can + // be removed once session history is stored exclusively in the parent + // process. + return mChildSessionHistory && mChildSessionHistory->IsInProcess() + ? mChildSessionHistory.get() + : nullptr; + } + + return mChildSessionHistory; +} + +void BrowsingContext::CreateChildSHistory() { + MOZ_ASSERT(IsTop()); + MOZ_ASSERT(GetHasSessionHistory()); + MOZ_DIAGNOSTIC_ASSERT(!mChildSessionHistory); + + // Because session history is global in a browsing context tree, every process + // that has access to a browsing context tree needs access to its session + // history. That is why we create the ChildSHistory object in every process + // where we have access to this browsing context (which is the top one). + mChildSessionHistory = new ChildSHistory(this); + + // If the top browsing context (this one) is loaded in this process then we + // also create the session history implementation for the child process. + // This can be removed once session history is stored exclusively in the + // parent process. + mChildSessionHistory->SetIsInProcess(IsInProcess()); +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + MOZ_ASSERT(GetHasSessionHistory() || !aOldValue, + "We don't support turning off session history."); + + if (GetHasSessionHistory() && !aOldValue) { + CreateChildSHistory(); + } +} + +bool BrowsingContext::CanSet( + FieldIndex, + const bool& aTargetTopLevelLinkClicksToBlankInternal, + ContentParent* aSource) { + return XRE_IsParentProcess() && !aSource && IsTop(); +} + +bool BrowsingContext::CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource) { + // We should only be able to set this for toplevel contexts which don't have + // an ID yet. + return GetBrowserId() == 0 && IsTop() && Children().IsEmpty(); +} + +bool BrowsingContext::CanSet(FieldIndex, + bool aNewValue, ContentParent* aSource) { + // Can only be cleared from `true` to `false`, and should only ever be set on + // the toplevel BrowsingContext. + return IsTop() && GetPendingInitialization() && !aNewValue; +} + +bool BrowsingContext::CanSet(FieldIndex, bool aNewValue, + ContentParent* aSource) { + return IsTop(); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aIsUnderHiddenEmbedderElement, + ContentParent* aSource) { + return true; +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + nsIDocShell* shell = GetDocShell(); + if (!shell) { + return; + } + + const bool newValue = IsUnderHiddenEmbedderElement(); + if (NS_WARN_IF(aOldValue == newValue)) { + return; + } + if (PresShell* presShell = shell->GetPresShell()) { + presShell->SetIsUnderHiddenEmbedderElement(newValue); + } + + // Propagate to children. + for (BrowsingContext* child : Children()) { + Element* embedderElement = child->GetEmbedderElement(); + if (!embedderElement) { + // TODO: We shouldn't need to null check here since `child` and the + // element returned by `child->GetEmbedderElement()` are in our + // process (the actual browsing context represented by `child` may not + // be, but that doesn't matter). However, there are currently a very + // small number of crashes due to `embedderElement` being null, somehow + // - see bug 1551241. For now we wallpaper the crash. + continue; + } + + bool embedderFrameIsHidden = true; + if (auto embedderFrame = embedderElement->GetPrimaryFrame()) { + embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible(); + } + + bool hidden = IsUnderHiddenEmbedderElement() || embedderFrameIsHidden; + if (child->IsUnderHiddenEmbedderElement() != hidden) { + Unused << child->SetIsUnderHiddenEmbedderElement(hidden); + } + } +} + +bool BrowsingContext::IsPopupAllowed() { + for (auto* context = GetCurrentWindowContext(); context; + context = context->GetParentWindowContext()) { + if (context->CanShowPopup()) { + return true; + } + } + + return false; +} + +/* static */ +bool BrowsingContext::ShouldAddEntryForRefresh( + nsIURI* aPreviousURI, const SessionHistoryInfo& aInfo) { + return ShouldAddEntryForRefresh(aPreviousURI, aInfo.GetURI(), + aInfo.HasPostData()); +} + +/* static */ +bool BrowsingContext::ShouldAddEntryForRefresh(nsIURI* aPreviousURI, + nsIURI* aNewURI, + bool aHasPostData) { + if (aHasPostData) { + return true; + } + + bool equalsURI = false; + if (aPreviousURI) { + aPreviousURI->Equals(aNewURI, &equalsURI); + } + return !equalsURI; +} + +void BrowsingContext::SessionHistoryCommit( + const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType, + nsIURI* aPreviousURI, SessionHistoryInfo* aPreviousActiveEntry, + bool aPersist, bool aCloneEntryChildren, bool aChannelExpired, + uint32_t aCacheKey) { + nsID changeID = {}; + if (XRE_IsContentProcess()) { + RefPtr rootSH = Top()->GetChildSessionHistory(); + if (rootSH) { + if (!aInfo.mLoadIsFromSessionHistory) { + // We try to mimic as closely as possible what will happen in + // CanonicalBrowsingContext::SessionHistoryCommit. We'll be + // incrementing the session history length if we're not replacing, + // this is a top-level load or it's not the initial load in an iframe, + // ShouldUpdateSessionHistory(loadType) returns true and it's not a + // refresh for which ShouldAddEntryForRefresh returns false. + // It is possible that this leads to wrong length temporarily, but + // so would not having the check for replace. + // Note that nsSHistory::AddEntry does a replace load if the current + // entry is not marked as a persisted entry. The child process does + // not have access to the current entry, so we use the previous active + // entry as the best approximation. When that's not the current entry + // then the length might be wrong briefly, until the parent process + // commits the actual length. + if (!LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) && + (IsTop() + ? (!aPreviousActiveEntry || aPreviousActiveEntry->GetPersist()) + : !!aPreviousActiveEntry) && + ShouldUpdateSessionHistory(aLoadType) && + (!LOAD_TYPE_HAS_FLAGS(aLoadType, + nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) || + ShouldAddEntryForRefresh(aPreviousURI, aInfo.mInfo))) { + changeID = rootSH->AddPendingHistoryChange(); + } + } else { + // History load doesn't change the length, only index. + changeID = rootSH->AddPendingHistoryChange(aInfo.mOffset, 0); + } + } + ContentChild* cc = ContentChild::GetSingleton(); + mozilla::Unused << cc->SendHistoryCommit( + this, aInfo.mLoadId, changeID, aLoadType, aPersist, aCloneEntryChildren, + aChannelExpired, aCacheKey); + } else { + Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType, + aPersist, aCloneEntryChildren, + aChannelExpired, aCacheKey); + } +} + +void BrowsingContext::SetActiveSessionHistoryEntry( + const Maybe& aPreviousScrollPos, SessionHistoryInfo* aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey, bool aUpdateLength) { + if (XRE_IsContentProcess()) { + // XXX Why we update cache key only in content process case? + if (aUpdatedCacheKey != 0) { + aInfo->SetCacheKey(aUpdatedCacheKey); + } + + nsID changeID = {}; + if (aUpdateLength) { + RefPtr shistory = Top()->GetChildSessionHistory(); + if (shistory) { + changeID = shistory->AddPendingHistoryChange(); + } + } + ContentChild::GetSingleton()->SendSetActiveSessionHistoryEntry( + this, aPreviousScrollPos, *aInfo, aLoadType, aUpdatedCacheKey, + changeID); + } else { + Canonical()->SetActiveSessionHistoryEntry( + aPreviousScrollPos, aInfo, aLoadType, aUpdatedCacheKey, nsID()); + } +} + +void BrowsingContext::ReplaceActiveSessionHistoryEntry( + SessionHistoryInfo* aInfo) { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendReplaceActiveSessionHistoryEntry(this, + *aInfo); + } else { + Canonical()->ReplaceActiveSessionHistoryEntry(aInfo); + } +} + +void BrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton() + ->SendRemoveDynEntriesFromActiveSessionHistoryEntry(this); + } else { + Canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry(); + } +} + +void BrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this, aChangeID); + } else { + Canonical()->RemoveFromSessionHistory(aChangeID); + } +} + +void BrowsingContext::HistoryGo( + int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction, + bool aUserActivation, std::function&&)>&& aResolver) { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendHistoryGo( + this, aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation, + std::move(aResolver), + [](mozilla::ipc:: + ResponseRejectReason) { /* FIXME Is ignoring this fine? */ }); + } else { + RefPtr self = Canonical(); + aResolver(self->HistoryGo( + aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation, + Canonical()->GetContentParent() + ? Some(Canonical()->GetContentParent()->ChildID()) + : Nothing())); + } +} + +void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) { + mChildSessionHistory = aChildSHistory; + mChildSessionHistory->SetBrowsingContext(this); + mFields.SetWithoutSyncing(true); +} + +bool BrowsingContext::ShouldUpdateSessionHistory(uint32_t aLoadType) { + // We don't update session history on reload unless we're loading + // an iframe in shift-reload case. + return nsDocShell::ShouldUpdateGlobalHistory(aLoadType) && + (!(aLoadType & nsIDocShell::LOAD_CMD_RELOAD) || + (IsForceReloadType(aLoadType) && IsSubframe())); +} + +nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) { + // We only rate limit non system callers + if (aCallerType == CallerType::System) { + return NS_OK; + } + + // Fetch rate limiting preferences + uint32_t limitCount = + StaticPrefs::dom_navigation_locationChangeRateLimit_count(); + uint32_t timeSpanSeconds = + StaticPrefs::dom_navigation_locationChangeRateLimit_timespan(); + + // Disable throttling if either of the preferences is set to 0. + if (limitCount == 0 || timeSpanSeconds == 0) { + return NS_OK; + } + + TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds); + + if (mLocationChangeRateLimitSpanStart.IsNull() || + ((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) { + // Initial call or timespan exceeded, reset counter and timespan. + mLocationChangeRateLimitSpanStart = TimeStamp::Now(); + mLocationChangeRateLimitCount = 1; + return NS_OK; + } + + if (mLocationChangeRateLimitCount >= limitCount) { + // Rate limit reached + + Document* doc = GetDocument(); + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc, + nsContentUtils::eDOM_PROPERTIES, + "LocChangeFloodingPrevented"); + } + + return NS_ERROR_DOM_SECURITY_ERR; + } + + mLocationChangeRateLimitCount++; + return NS_OK; +} + +void BrowsingContext::ResetLocationChangeRateLimit() { + // Resetting the timestamp object will cause the check function to + // init again and reset the rate limit. + mLocationChangeRateLimitSpanStart = TimeStamp(); +} + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits>::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::MaybeDiscarded& aParam) { + MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() || + aParam.GetMaybeDiscarded()->EverAttached()); + uint64_t id = aParam.ContextId(); + WriteIPDLParam(aWriter, aActor, id); +} + +bool IPDLParamTraits>::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + dom::MaybeDiscarded* aResult) { + uint64_t id = 0; + if (!ReadIPDLParam(aReader, aActor, &id)) { + return false; + } + + if (id == 0) { + *aResult = nullptr; + } else if (RefPtr bc = dom::BrowsingContext::Get(id)) { + *aResult = std::move(bc); + } else { + aResult->SetDiscarded(id); + } + return true; +} + +void IPDLParamTraits::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::BrowsingContext::IPCInitializer& aInit) { + // Write actor ID parameters. + WriteIPDLParam(aWriter, aActor, aInit.mId); + WriteIPDLParam(aWriter, aActor, aInit.mParentId); + WriteIPDLParam(aWriter, aActor, aInit.mWindowless); + WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteTabs); + WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteSubframes); + WriteIPDLParam(aWriter, aActor, aInit.mCreatedDynamically); + WriteIPDLParam(aWriter, aActor, aInit.mChildOffset); + WriteIPDLParam(aWriter, aActor, aInit.mOriginAttributes); + WriteIPDLParam(aWriter, aActor, aInit.mRequestContextId); + WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryIndex); + WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryCount); + WriteIPDLParam(aWriter, aActor, aInit.mFields); +} + +bool IPDLParamTraits::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + dom::BrowsingContext::IPCInitializer* aInit) { + // Read actor ID parameters. + if (!ReadIPDLParam(aReader, aActor, &aInit->mId) || + !ReadIPDLParam(aReader, aActor, &aInit->mParentId) || + !ReadIPDLParam(aReader, aActor, &aInit->mWindowless) || + !ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteTabs) || + !ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteSubframes) || + !ReadIPDLParam(aReader, aActor, &aInit->mCreatedDynamically) || + !ReadIPDLParam(aReader, aActor, &aInit->mChildOffset) || + !ReadIPDLParam(aReader, aActor, &aInit->mOriginAttributes) || + !ReadIPDLParam(aReader, aActor, &aInit->mRequestContextId) || + !ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryIndex) || + !ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryCount) || + !ReadIPDLParam(aReader, aActor, &aInit->mFields)) { + return false; + } + return true; +} + +template struct IPDLParamTraits; + +} // namespace ipc +} // namespace mozilla diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h new file mode 100644 index 0000000000..4c245337b7 --- /dev/null +++ b/docshell/base/BrowsingContext.h @@ -0,0 +1,1458 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContext_h +#define mozilla_dom_BrowsingContext_h + +#include +#include "GVAutoplayRequestUtils.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/HalScreenConfiguration.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Span.h" + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/LocationBase.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/ScreenOrientationBinding.h" +#include "mozilla/dom/SyncedContext.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocShell.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" +#include "nsILoadInfo.h" +#include "nsILoadContext.h" +#include "nsThreadUtils.h" + +class nsDocShellLoadState; +class nsGlobalWindowInner; +class nsGlobalWindowOuter; +class nsIPrincipal; +class nsOuterWindowProxy; +struct nsPoint; +class PickleIterator; + +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC + +namespace mozilla { + +class ErrorResult; +class LogModule; + +namespace ipc { +class IProtocol; +class IPCResult; + +template +struct IPDLParamTraits; +} // namespace ipc + +namespace dom { +class BrowsingContent; +class BrowsingContextGroup; +class CanonicalBrowsingContext; +class ChildSHistory; +class ContentParent; +class Element; +struct LoadingSessionHistoryInfo; +template +struct Nullable; +template +class Sequence; +class SessionHistoryInfo; +class SessionStorageManager; +class StructuredCloneHolder; +class WindowContext; +class WindowGlobalChild; +struct WindowPostMessageOptions; +class WindowProxyHolder; + +enum class ExplicitActiveStatus : uint8_t { + None, + Active, + Inactive, + EndGuard_, +}; + +struct EmbedderColorSchemes { + PrefersColorSchemeOverride mUsed{}; + PrefersColorSchemeOverride mPreferred{}; + + bool operator==(const EmbedderColorSchemes& aOther) const { + return mUsed == aOther.mUsed && mPreferred == aOther.mPreferred; + } + + bool operator!=(const EmbedderColorSchemes& aOther) const { + return !(*this == aOther); + } +}; + +// Fields are, by default, settable by any process and readable by any process. +// Racy sets will be resolved as-if they occurred in the order the parent +// process finds out about them. +// +// The `DidSet` and `CanSet` methods may be overloaded to provide different +// behavior for a specific field. +// * `DidSet` is called to run code in every process whenever the value is +// updated (This currently occurs even if the value didn't change, though +// this may change in the future). +// * `CanSet` is called before attempting to set the value, in both the process +// which calls `Set`, and the parent process, and will kill the misbehaving +// process if it fails. +#define MOZ_EACH_BC_FIELD(FIELD) \ + FIELD(Name, nsString) \ + FIELD(Closed, bool) \ + FIELD(ExplicitActive, ExplicitActiveStatus) \ + /* Top()-only. If true, new-playing media will be suspended when in an \ + * inactive browsing context. */ \ + FIELD(SuspendMediaWhenInactive, bool) \ + /* If true, we're within the nested event loop in window.open, and this \ + * context may not be used as the target of a load */ \ + FIELD(PendingInitialization, bool) \ + /* Indicates if the browser window is active for the purpose of the \ + * :-moz-window-inactive pseudoclass. Only read from or set on the \ + * top BrowsingContext. */ \ + FIELD(IsActiveBrowserWindowInternal, bool) \ + FIELD(OpenerPolicy, nsILoadInfo::CrossOriginOpenerPolicy) \ + /* Current opener for the BrowsingContext. Weak reference */ \ + FIELD(OpenerId, uint64_t) \ + FIELD(OnePermittedSandboxedNavigatorId, uint64_t) \ + /* WindowID of the inner window which embeds this BC */ \ + FIELD(EmbedderInnerWindowId, uint64_t) \ + FIELD(CurrentInnerWindowId, uint64_t) \ + FIELD(HadOriginalOpener, bool) \ + FIELD(IsPopupSpam, bool) \ + /* Hold the audio muted state and should be used on top level browsing \ + * contexts only */ \ + FIELD(Muted, bool) \ + /* Hold the pinned/app-tab state and should be used on top level browsing \ + * contexts only */ \ + FIELD(IsAppTab, bool) \ + /* Whether there's more than 1 tab / toplevel browsing context in this \ + * parent window. Used to determine if a given BC is allowed to resize \ + * and/or move the window or not. */ \ + FIELD(HasSiblings, bool) \ + /* Indicate that whether we should delay media playback, which would only \ + be done on an unvisited tab. And this should only be used on the top \ + level browsing contexts */ \ + FIELD(ShouldDelayMediaFromStart, bool) \ + /* See nsSandboxFlags.h for the possible flags. */ \ + FIELD(SandboxFlags, uint32_t) \ + /* The value of SandboxFlags when the BrowsingContext is first created. \ + * Used for sandboxing the initial about:blank document. */ \ + FIELD(InitialSandboxFlags, uint32_t) \ + /* A non-zero unique identifier for the browser element that is hosting \ + * this \ + * BrowsingContext tree. Every BrowsingContext in the element's tree will \ + * return the same ID in all processes and it will remain stable \ + * regardless of process changes. When a browser element's frameloader is \ + * switched to another browser element this ID will remain the same but \ + * hosted under the under the new browser element. */ \ + FIELD(BrowserId, uint64_t) \ + FIELD(HistoryID, nsID) \ + FIELD(InRDMPane, bool) \ + FIELD(Loading, bool) \ + /* A field only set on top browsing contexts, which indicates that either: \ + * \ + * * This is a browsing context created explicitly for printing or print \ + * preview (thus hosting static documents). \ + * \ + * * This is a browsing context where something in this tree is calling \ + * window.print() (and thus showing a modal dialog). \ + * \ + * We use it exclusively to block navigation for both of these cases. */ \ + FIELD(IsPrinting, bool) \ + FIELD(AncestorLoading, bool) \ + FIELD(AllowPlugins, bool) \ + FIELD(AllowContentRetargeting, bool) \ + FIELD(AllowContentRetargetingOnChildren, bool) \ + FIELD(ForceEnableTrackingProtection, bool) \ + FIELD(UseGlobalHistory, bool) \ + FIELD(TargetTopLevelLinkClicksToBlankInternal, bool) \ + FIELD(FullscreenAllowedByOwner, bool) \ + FIELD(ForceDesktopViewport, bool) \ + /* \ + * "is popup" in the spec. \ + * Set only on top browsing contexts. \ + * This doesn't indicate whether this is actually a popup or not, \ + * but whether this browsing context is created by requesting popup or not. \ + * See also: nsWindowWatcher::ShouldOpenPopup. \ + */ \ + FIELD(IsPopupRequested, bool) \ + /* These field are used to store the states of autoplay media request on \ + * GeckoView only, and it would only be modified on the top level browsing \ + * context. */ \ + FIELD(GVAudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + /* ScreenOrientation-related APIs */ \ + FIELD(CurrentOrientationAngle, float) \ + FIELD(CurrentOrientationType, mozilla::dom::OrientationType) \ + FIELD(OrientationLock, mozilla::hal::ScreenOrientation) \ + FIELD(UserAgentOverride, nsString) \ + FIELD(TouchEventsOverrideInternal, mozilla::dom::TouchEventsOverride) \ + FIELD(EmbedderElementType, Maybe) \ + FIELD(MessageManagerGroup, nsString) \ + FIELD(MaxTouchPointsOverride, uint8_t) \ + FIELD(FullZoom, float) \ + FIELD(WatchedByDevToolsInternal, bool) \ + FIELD(TextZoom, float) \ + FIELD(OverrideDPPX, float) \ + /* The current in-progress load. */ \ + FIELD(CurrentLoadIdentifier, Maybe) \ + /* See nsIRequest for possible flags. */ \ + FIELD(DefaultLoadFlags, uint32_t) \ + /* Signals that session history is enabled for this browsing context tree. \ + * This is only ever set to true on the top BC, so consumers need to get \ + * the value from the top BC! */ \ + FIELD(HasSessionHistory, bool) \ + /* Tracks if this context is the only top-level document in the session \ + * history of the context. */ \ + FIELD(IsSingleToplevelInHistory, bool) \ + FIELD(UseErrorPages, bool) \ + FIELD(PlatformOverride, nsString) \ + /* Specifies if this BC has loaded documents besides the initial \ + * about:blank document. about:privatebrowsing, about:home, about:newtab \ + * and non-initial about:blank are not considered to be initial \ + * documents. */ \ + FIELD(HasLoadedNonInitialDocument, bool) \ + /* Default value for nsIContentViewer::authorStyleDisabled in any new \ + * browsing contexts created as a descendant of this one. Valid only for \ + * top BCs. */ \ + FIELD(AuthorStyleDisabledDefault, bool) \ + FIELD(ServiceWorkersTestingEnabled, bool) \ + FIELD(MediumOverride, nsString) \ + /* DevTools override for prefers-color-scheme */ \ + FIELD(PrefersColorSchemeOverride, dom::PrefersColorSchemeOverride) \ + /* prefers-color-scheme override based on the color-scheme style of our \ + * embedder element. */ \ + FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \ + FIELD(DisplayMode, dom::DisplayMode) \ + /* The number of entries added to the session history because of this \ + * browsing context. */ \ + FIELD(HistoryEntryCount, uint32_t) \ + /* Don't use the getter of the field, but IsInBFCache() method */ \ + FIELD(IsInBFCache, bool) \ + FIELD(HasRestoreData, bool) \ + FIELD(SessionStoreEpoch, uint32_t) \ + /* Whether we can execute scripts in this BrowsingContext. Has no effect \ + * unless scripts are also allowed in the parent WindowContext. */ \ + FIELD(AllowJavascript, bool) \ + /* The count of request that are used to prevent the browsing context tree \ + * from being suspended, which would ONLY be modified on the top level \ + * context in the chrome process because that's a non-atomic counter */ \ + FIELD(PageAwakeRequestCount, uint32_t) \ + /* This field only gets incrememented when we start navigations in the \ + * parent process. This is used for keeping track of the racing navigations \ + * between the parent and content processes. */ \ + FIELD(ParentInitiatedNavigationEpoch, uint64_t) \ + /* This browsing context is for a synthetic image document wrapping an \ + * image embedded in or . */ \ + FIELD(SyntheticDocumentContainer, bool) \ + /* If true, this document is embedded within a content document, either \ + * loaded in the parent (e.g. about:addons or the devtools toolbox), or in \ + * a content process. */ \ + FIELD(EmbeddedInContentDocument, bool) \ + /* If true, this browsing context is within a hidden embedded document. */ \ + FIELD(IsUnderHiddenEmbedderElement, bool) + +// BrowsingContext, in this context, is the cross process replicated +// environment in which information about documents is stored. In +// particular the tree structure of nested browsing contexts is +// represented by the tree of BrowsingContexts. +// +// The tree of BrowsingContexts is created in step with its +// corresponding nsDocShell, and when nsDocShells are connected +// through a parent/child relationship, so are BrowsingContexts. The +// major difference is that BrowsingContexts are replicated (synced) +// to the parent process, making it possible to traverse the +// BrowsingContext tree for a tab, in both the parent and the child +// process. +// +// Trees of BrowsingContexts should only ever contain nodes of the +// same BrowsingContext::Type. This is enforced by asserts in the +// BrowsingContext::Create* methods. +class BrowsingContext : public nsILoadContext, public nsWrapperCache { + MOZ_DECL_SYNCED_CONTEXT(BrowsingContext, MOZ_EACH_BC_FIELD) + + public: + enum class Type { Chrome, Content }; + + static void Init(); + static LogModule* GetLog(); + static LogModule* GetSyncLog(); + + // Look up a BrowsingContext in the current process by ID. + static already_AddRefed Get(uint64_t aId); + static already_AddRefed Get(GlobalObject&, uint64_t aId) { + return Get(aId); + } + // Look up the top-level BrowsingContext by BrowserID. + static already_AddRefed GetCurrentTopByBrowserId( + uint64_t aBrowserId); + static already_AddRefed GetCurrentTopByBrowserId( + GlobalObject&, uint64_t aId) { + return GetCurrentTopByBrowserId(aId); + } + + static void UpdateCurrentTopByBrowserId(BrowsingContext* aNewBrowsingContext); + + static already_AddRefed GetFromWindow( + WindowProxyHolder& aProxy); + static already_AddRefed GetFromWindow( + GlobalObject&, WindowProxyHolder& aProxy) { + return GetFromWindow(aProxy); + } + + static void DiscardFromContentParent(ContentParent* aCP); + + // Create a brand-new toplevel BrowsingContext with no relationships to other + // BrowsingContexts, and which is not embedded within any or frame + // element. + // + // This BrowsingContext is immediately attached, and cannot have LoadContext + // flags customized unless it is of `Type::Chrome`. + // + // The process which created this BrowsingContext is responsible for detaching + // it. + static already_AddRefed CreateIndependent(Type aType); + + // Create a brand-new BrowsingContext object, but does not immediately attach + // it. State such as OriginAttributes and PrivateBrowsingId may be customized + // to configure the BrowsingContext before it is attached. + // + // `EnsureAttached()` must be called before the BrowsingContext is used for a + // DocShell, BrowserParent, or BrowserBridgeChild. + static already_AddRefed CreateDetached( + nsGlobalWindowInner* aParent, BrowsingContext* aOpener, + BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType, + bool aIsPopupRequested, bool aCreatedDynamically = false); + + void EnsureAttached(); + + bool EverAttached() const { return mEverAttached; } + + // Cast this object to a canonical browsing context, and return it. + CanonicalBrowsingContext* Canonical(); + + // Is the most recent Document in this BrowsingContext loaded within this + // process? This may be true with a null mDocShell after the Window has been + // closed. + bool IsInProcess() const { return mIsInProcess; } + + bool IsOwnedByProcess() const; + + bool CanHaveRemoteOuterProxies() const { + return !mIsInProcess || mDanglingRemoteOuterProxies; + } + + // Has this BrowsingContext been discarded. A discarded browsing context has + // been destroyed, and may not be available on the other side of an IPC + // message. + bool IsDiscarded() const { return mIsDiscarded; } + + // Returns true if none of the BrowsingContext's ancestor BrowsingContexts or + // WindowContexts are discarded or cached. + bool AncestorsAreCurrent() const; + + bool Windowless() const { return mWindowless; } + + // Get the DocShell for this BrowsingContext if it is in-process, or + // null if it's not. + nsIDocShell* GetDocShell() const { return mDocShell; } + void SetDocShell(nsIDocShell* aDocShell); + void ClearDocShell() { mDocShell = nullptr; } + + // Get the Document for this BrowsingContext if it is in-process, or + // null if it's not. + Document* GetDocument() const { + return mDocShell ? mDocShell->GetDocument() : nullptr; + } + Document* GetExtantDocument() const { + return mDocShell ? mDocShell->GetExtantDocument() : nullptr; + } + + // This cleans up remote outer window proxies that might have been left behind + // when the browsing context went from being remote to local. It does this by + // turning them into cross-compartment wrappers to aOuter. If there is already + // a remote proxy in the compartment of aOuter, then aOuter will get swapped + // to it and the value of aOuter will be set to the object that used to be the + // remote proxy and is now an OuterWindowProxy. + void CleanUpDanglingRemoteOuterWindowProxies( + JSContext* aCx, JS::MutableHandle aOuter); + + // Get the embedder element for this BrowsingContext if the embedder is + // in-process, or null if it's not. + Element* GetEmbedderElement() const { return mEmbedderElement; } + void SetEmbedderElement(Element* aEmbedder); + + // Return true if the type of the embedder element is either object + // or embed, false otherwise. + bool IsEmbedderTypeObjectOrEmbed(); + + // Called after the BrowingContext has been embedded in a FrameLoader. This + // happens after `SetEmbedderElement` is called on the BrowsingContext and + // after the BrowsingContext has been set on the FrameLoader. + void Embed(); + + // Get the outer window object for this BrowsingContext if it is in-process + // and still has a docshell, or null otherwise. + nsPIDOMWindowOuter* GetDOMWindow() const { + return mDocShell ? mDocShell->GetWindow() : nullptr; + } + + uint64_t GetRequestContextId() const { return mRequestContextId; } + + // Detach the current BrowsingContext from its parent, in both the + // child and the parent process. + void Detach(bool aFromIPC = false); + + // Prepare this BrowsingContext to leave the current process. + void PrepareForProcessChange(); + + // Triggers a load in the process which currently owns this BrowsingContext. + nsresult LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating = false); + + nsresult InternalLoad(nsDocShellLoadState* aLoadState); + + // Removes the root document for this BrowsingContext tree from the BFCache, + // if it is cached, and returns true if it was. + bool RemoveRootFromBFCacheSync(); + + // If the load state includes a source BrowsingContext has been passed, check + // to see if we are sandboxed from it as the result of an iframe or CSP + // sandbox. + nsresult CheckSandboxFlags(nsDocShellLoadState* aLoadState); + + void DisplayLoadError(const nsAString& aURI); + + // Check that this browsing context is targetable for navigations (i.e. that + // it is neither closed, cached, nor discarded). + bool IsTargetable() const; + + // True if this browsing context is inactive and is able to be suspended. + bool InactiveForSuspend() const; + + const nsString& Name() const { return GetName(); } + void GetName(nsAString& aName) { aName = GetName(); } + bool NameEquals(const nsAString& aName) { return GetName().Equals(aName); } + + Type GetType() const { return mType; } + bool IsContent() const { return mType == Type::Content; } + bool IsChrome() const { return !IsContent(); } + + bool IsTop() const { return !GetParent(); } + bool IsSubframe() const { return !IsTop(); } + + bool IsTopContent() const { return IsContent() && IsTop(); } + + bool IsInSubtreeOf(BrowsingContext* aContext); + + bool IsContentSubframe() const { return IsContent() && IsSubframe(); } + + // non-zero + uint64_t Id() const { return mBrowsingContextId; } + + BrowsingContext* GetParent() const; + BrowsingContext* Top(); + const BrowsingContext* Top() const; + + int32_t IndexOf(BrowsingContext* aChild); + + // NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not + // cross toplevel content browser boundaries. + WindowContext* GetParentWindowContext() const { return mParentWindow; } + WindowContext* GetTopWindowContext() const; + + already_AddRefed GetOpener() const { + RefPtr opener(Get(GetOpenerId())); + if (!mIsDiscarded && opener && !opener->mIsDiscarded) { + MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType); + return opener.forget(); + } + return nullptr; + } + void SetOpener(BrowsingContext* aOpener) { + MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->Group() == Group()); + MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->mType == mType); + + MOZ_ALWAYS_SUCCEEDS(SetOpenerId(aOpener ? aOpener->Id() : 0)); + } + + bool HasOpener() const; + + bool HadOriginalOpener() const { return GetHadOriginalOpener(); } + + // Returns true if the browsing context and top context are same origin + bool SameOriginWithTop(); + + /** + * When a new browsing context is opened by a sandboxed document, it needs to + * keep track of the browsing context that opened it, so that it can be + * navigated by it. This is the "one permitted sandboxed navigator". + */ + already_AddRefed GetOnePermittedSandboxedNavigator() const { + return Get(GetOnePermittedSandboxedNavigatorId()); + } + [[nodiscard]] nsresult SetOnePermittedSandboxedNavigator( + BrowsingContext* aNavigator) { + if (GetOnePermittedSandboxedNavigatorId()) { + MOZ_ASSERT(false, + "One Permitted Sandboxed Navigator should only be set once."); + return NS_ERROR_FAILURE; + } else { + return SetOnePermittedSandboxedNavigatorId(aNavigator ? aNavigator->Id() + : 0); + } + } + + uint32_t SandboxFlags() const { return GetSandboxFlags(); } + + Span> Children() const; + void GetChildren(nsTArray>& aChildren); + + Span> NonSyntheticChildren() const; + + const nsTArray>& GetWindowContexts() { + return mWindowContexts; + } + void GetWindowContexts(nsTArray>& aWindows); + + void RegisterWindowContext(WindowContext* aWindow); + void UnregisterWindowContext(WindowContext* aWindow); + WindowContext* GetCurrentWindowContext() const { + return mCurrentWindowContext; + } + + // Helpers to traverse this BrowsingContext subtree. Note that these will only + // traverse active contexts, and will ignore ones in the BFCache. + enum class WalkFlag { + Next, + Skip, + Stop, + }; + + /** + * Walk the browsing context tree in pre-order and call `aCallback` + * for every node in the tree. PreOrderWalk accepts two types of + * callbacks, either of the type `void(BrowsingContext*)` or + * `WalkFlag(BrowsingContext*)`. The former traverses the entire + * tree, but the latter let's you control if a sub-tree should be + * skipped by returning `WalkFlag::Skip`, completely abort traversal + * by returning `WalkFlag::Stop` or continue as normal with + * `WalkFlag::Next`. + */ + template + void PreOrderWalk(F&& aCallback) { + if constexpr (std::is_void_v< + typename std::invoke_result_t>) { + PreOrderWalkVoid(std::forward(aCallback)); + } else { + PreOrderWalkFlag(std::forward(aCallback)); + } + } + + void PreOrderWalkVoid(const std::function& aCallback); + WalkFlag PreOrderWalkFlag( + const std::function& aCallback); + + void PostOrderWalk(const std::function& aCallback); + + void GetAllBrowsingContextsInSubtree( + nsTArray>& aBrowsingContexts); + + BrowsingContextGroup* Group() { return mGroup; } + + // WebIDL bindings for nsILoadContext + Nullable GetAssociatedWindow(); + Nullable GetTopWindow(); + Element* GetTopFrameElement(); + bool GetIsContent() { return IsContent(); } + void SetUsePrivateBrowsing(bool aUsePrivateBrowsing, ErrorResult& aError); + // Needs a different name to disambiguate from the xpidl method with + // the same signature but different return value. + void SetUseTrackingProtectionWebIDL(bool aUseTrackingProtection, + ErrorResult& aRv); + bool UseTrackingProtectionWebIDL() { return UseTrackingProtection(); } + void GetOriginAttributes(JSContext* aCx, JS::MutableHandle aVal, + ErrorResult& aError); + + bool InRDMPane() const { return GetInRDMPane(); } + + bool WatchedByDevTools(); + void SetWatchedByDevTools(bool aWatchedByDevTools, ErrorResult& aRv); + + dom::TouchEventsOverride TouchEventsOverride() const; + bool TargetTopLevelLinkClicksToBlank() const; + + bool FullscreenAllowed() const; + + float FullZoom() const { return GetFullZoom(); } + float TextZoom() const { return GetTextZoom(); } + + float OverrideDPPX() const { return Top()->GetOverrideDPPX(); } + + bool SuspendMediaWhenInactive() const { + return GetSuspendMediaWhenInactive(); + } + + bool IsActive() const; + void SetIsActive(bool aIsActive, mozilla::ErrorResult& aRv) { + SetExplicitActive(aIsActive ? ExplicitActiveStatus::Active + : ExplicitActiveStatus::Inactive, + aRv); + } + + bool ForceDesktopViewport() const { return GetForceDesktopViewport(); } + + bool AuthorStyleDisabledDefault() const { + return GetAuthorStyleDisabledDefault(); + } + + bool UseGlobalHistory() const { return GetUseGlobalHistory(); } + + bool GetIsActiveBrowserWindow(); + + void SetIsActiveBrowserWindow(bool aActive); + + uint64_t BrowserId() const { return GetBrowserId(); } + + bool IsLoading(); + + void GetEmbedderElementType(nsString& aElementType) { + if (GetEmbedderElementType().isSome()) { + aElementType = GetEmbedderElementType().value(); + } + } + + bool IsLoadingIdentifier(uint64_t aLoadIdentifer) { + if (GetCurrentLoadIdentifier() && + *GetCurrentLoadIdentifier() == aLoadIdentifer) { + return true; + } + return false; + } + + // ScreenOrientation related APIs + [[nodiscard]] nsresult SetCurrentOrientation(OrientationType aType, + float aAngle) { + Transaction txn; + txn.SetCurrentOrientationType(aType); + txn.SetCurrentOrientationAngle(aAngle); + return txn.Commit(this); + } + + void SetRDMPaneOrientation(OrientationType aType, float aAngle, + ErrorResult& aRv) { + if (InRDMPane()) { + if (NS_FAILED(SetCurrentOrientation(aType, aAngle))) { + aRv.ThrowInvalidStateError("Browsing context is discarded"); + } + } + } + + void SetRDMPaneMaxTouchPoints(uint8_t aMaxTouchPoints, ErrorResult& aRv) { + if (InRDMPane()) { + SetMaxTouchPointsOverride(aMaxTouchPoints, aRv); + } + } + + // Find a browsing context in this context's list of + // children. Doesn't consider the special names, '_self', '_parent', + // '_top', or '_blank'. Performs access control checks with regard to + // 'this'. + BrowsingContext* FindChildWithName(const nsAString& aName, + WindowGlobalChild& aRequestingWindow); + + // Find a browsing context in the subtree rooted at 'this' Doesn't + // consider the special names, '_self', '_parent', '_top', or + // '_blank'. + // + // If passed, performs access control checks with regard to + // 'aRequestingContext', otherwise performs no access checks. + BrowsingContext* FindWithNameInSubtree(const nsAString& aName, + WindowGlobalChild* aRequestingWindow); + + // Find the special browsing context if aName is '_self', '_parent', + // '_top', but not '_blank'. The latter is handled in FindWithName + BrowsingContext* FindWithSpecialName(const nsAString& aName, + WindowGlobalChild& aRequestingWindow); + + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Return the window proxy object that corresponds to this browsing context. + inline JSObject* GetWindowProxy() const { return mWindowProxy; } + inline JSObject* GetUnbarrieredWindowProxy() const { + return mWindowProxy.unbarrieredGet(); + } + + // Set the window proxy object that corresponds to this browsing context. + void SetWindowProxy(JS::Handle aWindowProxy) { + mWindowProxy = aWindowProxy; + } + + Nullable GetWindow(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(BrowsingContext) + NS_DECL_NSILOADCONTEXT + + // Window APIs that are cross-origin-accessible (from the HTML spec). + WindowProxyHolder Window(); + BrowsingContext* GetBrowsingContext() { return this; }; + BrowsingContext* Self() { return this; } + void Location(JSContext* aCx, JS::MutableHandle aLocation, + ErrorResult& aError); + void Close(CallerType aCallerType, ErrorResult& aError); + bool GetClosed(ErrorResult&) { return GetClosed(); } + void Focus(CallerType aCallerType, ErrorResult& aError); + void Blur(CallerType aCallerType, ErrorResult& aError); + WindowProxyHolder GetFrames(ErrorResult& aError); + int32_t Length() const { return Children().Length(); } + Nullable GetTop(ErrorResult& aError); + void GetOpener(JSContext* aCx, JS::MutableHandle aOpener, + ErrorResult& aError) const; + Nullable GetParent(ErrorResult& aError); + void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, + const nsAString& aTargetOrigin, + const Sequence& aTransfer, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aError); + void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, + const WindowPostMessageOptions& aOptions, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aError); + + void GetCustomUserAgent(nsAString& aUserAgent) { + aUserAgent = Top()->GetUserAgentOverride(); + } + nsresult SetCustomUserAgent(const nsAString& aUserAgent); + void SetCustomUserAgent(const nsAString& aUserAgent, ErrorResult& aRv); + + void GetCustomPlatform(nsAString& aPlatform) { + aPlatform = Top()->GetPlatformOverride(); + } + void SetCustomPlatform(const nsAString& aPlatform, ErrorResult& aRv); + + JSObject* WrapObject(JSContext* aCx); + + static JSObject* ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + StructuredCloneHolder* aHolder); + bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder); + + void StartDelayedAutoplayMediaComponents(); + + [[nodiscard]] nsresult ResetGVAutoplayRequestStatus(); + + /** + * Information required to initialize a BrowsingContext in another process. + * This object may be serialized over IPC. + */ + struct IPCInitializer { + uint64_t mId = 0; + + // IDs are used for Parent and Opener to allow for this object to be + // deserialized before other BrowsingContext in the BrowsingContextGroup + // have been initialized. + uint64_t mParentId = 0; + already_AddRefed GetParent(); + already_AddRefed GetOpener(); + + uint64_t GetOpenerId() const { return mFields.Get(); } + + bool mWindowless = false; + bool mUseRemoteTabs = false; + bool mUseRemoteSubframes = false; + bool mCreatedDynamically = false; + int32_t mChildOffset = 0; + int32_t mSessionHistoryIndex = -1; + int32_t mSessionHistoryCount = 0; + OriginAttributes mOriginAttributes; + uint64_t mRequestContextId = 0; + + FieldValues mFields; + }; + + // Create an IPCInitializer object for this BrowsingContext. + IPCInitializer GetIPCInitializer(); + + // Create a BrowsingContext object from over IPC. + static mozilla::ipc::IPCResult CreateFromIPC(IPCInitializer&& aInitializer, + BrowsingContextGroup* aGroup, + ContentParent* aOriginProcess); + + bool IsSandboxedFrom(BrowsingContext* aTarget); + + // The runnable will be called once there is idle time, or the top level + // page has been loaded or if a timeout has fired. + // Must be called only on the top level BrowsingContext. + void AddDeprioritizedLoadRunner(nsIRunnable* aRunner); + + RefPtr GetSessionStorageManager(); + + // Set PendingInitialization on this BrowsingContext before the context has + // been attached. + void InitPendingInitialization(bool aPendingInitialization) { + MOZ_ASSERT(!EverAttached()); + mFields.SetWithoutSyncing( + aPendingInitialization); + } + + bool CreatedDynamically() const { return mCreatedDynamically; } + + // Returns true if this browsing context, or any ancestor to this browsing + // context was created dynamically. See also `CreatedDynamically`. + bool IsDynamic() const; + + int32_t ChildOffset() const { return mChildOffset; } + + bool GetOffsetPath(nsTArray& aPath) const; + + const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; } + nsresult SetOriginAttributes(const OriginAttributes& aAttrs); + + void GetHistoryID(JSContext* aCx, JS::MutableHandle aVal, + ErrorResult& aError); + + // This should only be called on the top browsing context. + void InitSessionHistory(); + + // This will only ever return a non-null value if called on the top browsing + // context. + ChildSHistory* GetChildSessionHistory(); + + bool CrossOriginIsolated(); + + // Check if it is allowed to open a popup from the current browsing + // context or any of its ancestors. + bool IsPopupAllowed(); + + // aCurrentURI is only required to be non-null if the load type contains the + // nsIWebNavigation::LOAD_FLAGS_IS_REFRESH flag and aInfo is for a refresh to + // the current URI. + void SessionHistoryCommit(const LoadingSessionHistoryInfo& aInfo, + uint32_t aLoadType, nsIURI* aCurrentURI, + SessionHistoryInfo* aPreviousActiveEntry, + bool aPersist, bool aCloneEntryChildren, + bool aChannelExpired, uint32_t aCacheKey); + + // Set a new active entry on this browsing context. This is used for + // implementing history.pushState/replaceState and same document navigations. + // The new active entry will be linked to the current active entry through + // its shared state. + // aPreviousScrollPos is the scroll position that needs to be saved on the + // previous active entry. + // aUpdatedCacheKey is the cache key to set on the new active entry. If + // aUpdatedCacheKey is 0 then it will be ignored. + void SetActiveSessionHistoryEntry(const Maybe& aPreviousScrollPos, + SessionHistoryInfo* aInfo, + uint32_t aLoadType, + uint32_t aUpdatedCacheKey, + bool aUpdateLength = true); + + // Replace the active entry for this browsing context. This is used for + // implementing history.replaceState and same document navigations. + void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo); + + // Removes dynamic child entries of the active entry. + void RemoveDynEntriesFromActiveSessionHistoryEntry(); + + // Removes entries corresponding to this BrowsingContext from session history. + void RemoveFromSessionHistory(const nsID& aChangeID); + + void SetTriggeringAndInheritPrincipals(nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint64_t aLoadIdentifier); + + // Return mTriggeringPrincipal and mPrincipalToInherit if the load id + // saved with the principal matches the current load identifier of this BC. + std::tuple, nsCOMPtr> + GetTriggeringAndInheritPrincipalsForCurrentLoad(); + + void HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, bool aUserActivation, + std::function&&)>&& aResolver); + + bool ShouldUpdateSessionHistory(uint32_t aLoadType); + + // Checks if we reached the rate limit for calls to Location and History API. + // The rate limit is controlled by the + // "dom.navigation.locationChangeRateLimit" prefs. + // Rate limit applies per BrowsingContext. + // Returns NS_OK if we are below the rate limit and increments the counter. + // Returns NS_ERROR_DOM_SECURITY_ERR if limit is reached. + nsresult CheckLocationChangeRateLimit(CallerType aCallerType); + + void ResetLocationChangeRateLimit(); + + mozilla::dom::DisplayMode DisplayMode() { return Top()->GetDisplayMode(); } + + // Returns canFocus, isActive + std::tuple CanFocusCheck(CallerType aCallerType); + + bool CanBlurCheck(CallerType aCallerType); + + PopupBlocker::PopupControlState RevisePopupAbuseLevel( + PopupBlocker::PopupControlState aControl); + + void IncrementHistoryEntryCountForBrowsingContext(); + + bool ServiceWorkersTestingEnabled() const { + return GetServiceWorkersTestingEnabled(); + } + + void GetMediumOverride(nsAString& aOverride) const { + aOverride = GetMediumOverride(); + } + + dom::PrefersColorSchemeOverride PrefersColorSchemeOverride() const { + return GetPrefersColorSchemeOverride(); + } + + bool IsInBFCache() const; + + bool AllowJavascript() const { return GetAllowJavascript(); } + bool CanExecuteScripts() const { return mCanExecuteScripts; } + + uint32_t DefaultLoadFlags() const { return GetDefaultLoadFlags(); } + + // When request for page awake, it would increase a count that is used to + // prevent whole browsing context tree from being suspended. The request can + // be called multiple times. When calling the revoke, it would decrease the + // count and once the count reaches to zero, the browsing context tree could + // be suspended when the tree is inactive. + void RequestForPageAwake(); + void RevokeForPageAwake(); + + void AddDiscardListener(std::function&& aListener); + + bool IsAppTab() { return GetIsAppTab(); } + bool HasSiblings() { return GetHasSiblings(); } + + bool IsUnderHiddenEmbedderElement() const { + return GetIsUnderHiddenEmbedderElement(); + } + + protected: + virtual ~BrowsingContext(); + BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit); + + void SetChildSHistory(ChildSHistory* aChildSHistory); + already_AddRefed ForgetChildSHistory() { + // FIXME Do we need to unset mHasSessionHistory? + return mChildSessionHistory.forget(); + } + + static bool ShouldAddEntryForRefresh(nsIURI* aCurrentURI, + const SessionHistoryInfo& aInfo); + static bool ShouldAddEntryForRefresh(nsIURI* aCurrentURI, nsIURI* aNewURI, + bool aHasPostData); + + private: + mozilla::ipc::IPCResult Attach(bool aFromIPC, ContentParent* aOriginProcess); + + // Recomputes whether we can execute scripts in this BrowsingContext based on + // the value of AllowJavascript() and whether scripts are allowed in the + // parent WindowContext. Called whenever the AllowJavascript() flag or the + // parent WC changes. + void RecomputeCanExecuteScripts(); + + // Is it early enough in the BrowsingContext's lifecycle that it is still + // OK to set OriginAttributes? + bool CanSetOriginAttributes(); + + void AssertOriginAttributesMatchPrivateBrowsing(); + + // Assert that the BrowsingContext's LoadContext flags appear coherent + // relative to related BrowsingContexts. + void AssertCoherentLoadContext(); + + friend class ::nsOuterWindowProxy; + friend class ::nsGlobalWindowOuter; + friend class WindowContext; + + // Update the window proxy object that corresponds to this browsing context. + // This should be called from the window proxy object's objectMoved hook, if + // the object mWindowProxy points to was moved by the JS GC. + void UpdateWindowProxy(JSObject* obj, JSObject* old) { + if (mWindowProxy) { + MOZ_ASSERT(mWindowProxy == old); + mWindowProxy = obj; + } + } + // Clear the window proxy object that corresponds to this browsing context. + // This should be called if the window proxy object is finalized, or it can't + // reach its browsing context anymore. + void ClearWindowProxy() { mWindowProxy = nullptr; } + + friend class Location; + friend class RemoteLocationProxy; + /** + * LocationProxy is the class for the native object stored as a private in a + * RemoteLocationProxy proxy representing a Location object in a different + * process. It forwards all operations to its BrowsingContext and aggregates + * its refcount to that BrowsingContext. + */ + class LocationProxy final : public LocationBase { + public: + MozExternalRefCountType AddRef() { return GetBrowsingContext()->AddRef(); } + MozExternalRefCountType Release() { + return GetBrowsingContext()->Release(); + } + + protected: + friend class RemoteLocationProxy; + BrowsingContext* GetBrowsingContext() override { + return reinterpret_cast( + uintptr_t(this) - offsetof(BrowsingContext, mLocation)); + } + + already_AddRefed GetDocShell() override { return nullptr; } + }; + + // Send a given `BaseTransaction` object to the correct remote. + void SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, uint64_t aEpoch); + void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, + uint64_t aEpoch); + + bool CanSet(FieldIndex, uint32_t aEpoch, + ContentParent* aSource) { + return IsTop() && !aSource; + } + + void DidSet(FieldIndex, uint32_t aOldValue); + + using CanSetResult = syncedcontext::CanSetResult; + + // Ensure that opener is in the same BrowsingContextGroup. + bool CanSet(FieldIndex, const uint64_t& aValue, + ContentParent* aSource) { + if (aValue != 0) { + RefPtr opener = Get(aValue); + return opener && opener->Group() == Group(); + } + return true; + } + + bool CanSet(FieldIndex, + nsILoadInfo::CrossOriginOpenerPolicy, ContentParent*); + + bool CanSet(FieldIndex, bool, + ContentParent*) { + return IsTop(); + } + + bool CanSet(FieldIndex, const nsString&, ContentParent*) { + return IsTop(); + } + + bool CanSet(FieldIndex, const EmbedderColorSchemes&, + ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); + } + + bool CanSet(FieldIndex, + dom::PrefersColorSchemeOverride, ContentParent*) { + return IsTop(); + } + + void DidSet(FieldIndex, bool aOldValue); + + void DidSet(FieldIndex, + EmbedderColorSchemes&& aOldValue); + + void DidSet(FieldIndex, + dom::PrefersColorSchemeOverride aOldValue); + + template + void WalkPresContexts(Callback&&); + void PresContextAffectingFieldChanged(); + + void DidSet(FieldIndex, nsString&& aOldValue); + + bool CanSet(FieldIndex, bool, ContentParent*) { + return IsTop(); + } + + bool CanSet(FieldIndex, + dom::TouchEventsOverride aTouchEventsOverride, + ContentParent* aSource); + void DidSet(FieldIndex, + dom::TouchEventsOverride&& aOldValue); + + bool CanSet(FieldIndex, const enum DisplayMode& aDisplayMode, + ContentParent* aSource) { + return IsTop(); + } + + void DidSet(FieldIndex, enum DisplayMode aOldValue); + + void DidSet(FieldIndex, ExplicitActiveStatus aOldValue); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); + + // Ensure that we only set the flag on the top level browsingContext. + // And then, we do a pre-order walk in the tree to refresh the + // volume of all media elements. + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, const float& aValue, + ContentParent* aSource); + void DidSet(FieldIndex, float aOldValue); + + bool CanSet(FieldIndex, const uint64_t& aValue, + ContentParent* aSource); + + CanSetResult CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource); + + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + CanSetResult CanSet(FieldIndex, + const nsString& aPlatformOverride, + ContentParent* aSource); + + void DidSet(FieldIndex); + CanSetResult CanSet(FieldIndex, + const nsString& aUserAgent, ContentParent* aSource); + bool CanSet(FieldIndex, + const mozilla::hal::ScreenOrientation& aOrientationLock, + ContentParent* aSource); + + bool CanSet(FieldIndex, + const Maybe& aInitiatorType, ContentParent* aSource); + + bool CanSet(FieldIndex, + const nsString& aMessageManagerGroup, ContentParent* aSource); + + CanSetResult CanSet(FieldIndex, + const bool& aAllowContentRetargeting, + ContentParent* aSource); + CanSetResult CanSet(FieldIndex, + const bool& aAllowContentRetargetingOnChildren, + ContentParent* aSource); + CanSetResult CanSet(FieldIndex, const bool& aAllowPlugins, + ContentParent* aSource); + bool CanSet(FieldIndex, const bool&, + ContentParent*); + bool CanSet(FieldIndex, + const bool& aWatchedByDevToolsInternal, ContentParent* aSource); + + CanSetResult CanSet(FieldIndex, + const uint32_t& aDefaultLoadFlags, + ContentParent* aSource); + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, const bool& aUseGlobalHistory, + ContentParent* aSource); + + bool CanSet(FieldIndex, + const bool& aTargetTopLevelLinkClicksToBlankInternal, + ContentParent* aSource); + + void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aUseErrorPages, + ContentParent* aSource); + + bool CanSet(FieldIndex, bool aNewValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, uint32_t aNewValue, + ContentParent* aSource); + void DidSet(FieldIndex, uint32_t aOldValue); + + CanSetResult CanSet(FieldIndex, bool aValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, bool aValue, + ContentParent* aSource) { + return IsTop() && XRE_IsParentProcess(); + } + + // TODO(emilio): Maybe handle the flag being set dynamically without + // navigating? The previous code didn't do it tho, and a reload is probably + // worth it regardless. + // void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, bool aNewValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, + const bool& aIsUnderHiddenEmbedderElement, + ContentParent* aSource); + + bool CanSet(FieldIndex, bool, + ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); + } + + template + bool CanSet(FieldIndex, const T&, ContentParent*) { + return true; + } + + // Overload `DidSet` to get notifications for a particular field being set. + // + // You can also overload the variant that gets the old value if you need it. + template + void DidSet(FieldIndex) {} + template + void DidSet(FieldIndex, T&& aOldValue) {} + + void DidSet(FieldIndex, float aOldValue); + void DidSet(FieldIndex, float aOldValue); + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, bool, ContentParent* aSource); + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex, bool aOldValue); + + // Allow if the process attemping to set field is the same as the owning + // process. Deprecated. New code that might use this should generally be moved + // to WindowContext or be settable only by the parent process. + CanSetResult LegacyRevertIfNotOwningOrParentProcess(ContentParent* aSource); + + // True if the process attempting to set field is the same as the embedder's + // process. + bool CheckOnlyEmbedderCanSet(ContentParent* aSource); + + void CreateChildSHistory(); + + using PrincipalWithLoadIdentifierTuple = + std::tuple, uint64_t>; + + nsIPrincipal* GetSavedPrincipal( + Maybe aPrincipalTuple); + + // Type of BrowsingContent + const Type mType; + + // Unique id identifying BrowsingContext + const uint64_t mBrowsingContextId; + + RefPtr mGroup; + RefPtr mParentWindow; + nsCOMPtr mDocShell; + + RefPtr mEmbedderElement; + + nsTArray> mWindowContexts; + RefPtr mCurrentWindowContext; + + // This is not a strong reference, but using a JS::Heap for that should be + // fine. The JSObject stored in here should be a proxy with a + // nsOuterWindowProxy handler, which will update the pointer from its + // objectMoved hook and clear it from its finalize hook. + JS::Heap mWindowProxy; + LocationProxy mLocation; + + // OriginAttributes for this BrowsingContext. May not be changed after this + // BrowsingContext is attached. + OriginAttributes mOriginAttributes; + + // The network request context id, representing the nsIRequestContext + // associated with this BrowsingContext, and LoadGroups created for it. + uint64_t mRequestContextId = 0; + + // Determines if private browsing should be used. May not be changed after + // this BrowsingContext is attached. This field matches mOriginAttributes in + // content Browsing Contexts. Currently treated as a binary value: 1 - in + // private mode, 0 - not private mode. + uint32_t mPrivateBrowsingId; + + // True if Attach() has been called on this BrowsingContext already. + bool mEverAttached : 1; + + // Is the most recent Document in this BrowsingContext loaded within this + // process? This may be true with a null mDocShell after the Window has been + // closed. + bool mIsInProcess : 1; + + // Has this browsing context been discarded? BrowsingContexts should + // only be discarded once. + bool mIsDiscarded : 1; + + // True if this BrowsingContext has no associated visible window, and is owned + // by whichever process created it, even if top-level. + bool mWindowless : 1; + + // This is true if the BrowsingContext was out of process, but is now in + // process, and might have remote window proxies that need to be cleaned up. + bool mDanglingRemoteOuterProxies : 1; + + // True if this BrowsingContext has been embedded in a element in this + // process. + bool mEmbeddedByThisProcess : 1; + + // Determines if remote (out-of-process) tabs should be used. May not be + // changed after this BrowsingContext is attached. + bool mUseRemoteTabs : 1; + + // Determines if out-of-process iframes should be used. May not be changed + // after this BrowsingContext is attached. + bool mUseRemoteSubframes : 1; + + // True if this BrowsingContext is for a frame that was added dynamically. + bool mCreatedDynamically : 1; + + // Set to true if the browsing context is in the bfcache and pagehide has been + // dispatched. When coming out from the bfcache, the value is set to false + // before dispatching pageshow. + bool mIsInBFCache : 1; + + // Determines if we can execute scripts in this BrowsingContext. True if + // AllowJavascript() is true and script execution is allowed in the parent + // WindowContext. + bool mCanExecuteScripts : 1; + + // The original offset of this context in its container. This property is -1 + // if this BrowsingContext is for a frame that was added dynamically. + int32_t mChildOffset; + + // The start time of user gesture, this is only available if the browsing + // context is in process. + TimeStamp mUserGestureStart; + + // Triggering principal and principal to inherit need to point to original + // principal instances if the document is loaded in the same process as the + // process that initiated the load. When the load starts we save the + // principals along with the current load id. + // These principals correspond to the most recent load that took place within + // the process of this browsing context. + Maybe mTriggeringPrincipal; + Maybe mPrincipalToInherit; + + class DeprioritizedLoadRunner + : public mozilla::Runnable, + public mozilla::LinkedListElement { + public: + explicit DeprioritizedLoadRunner(nsIRunnable* aInner) + : Runnable("DeprioritizedLoadRunner"), mInner(aInner) {} + + NS_IMETHOD Run() override { + if (mInner) { + RefPtr inner = std::move(mInner); + inner->Run(); + } + + return NS_OK; + } + + private: + RefPtr mInner; + }; + + mozilla::LinkedList mDeprioritizedLoadRunner; + + RefPtr mSessionStorageManager; + RefPtr mChildSessionHistory; + + nsTArray> mDiscardListeners; + + // Counter and time span for rate limiting Location and History API calls. + // Used by CheckLocationChangeRateLimit. Do not apply cross-process. + uint32_t mLocationChangeRateLimitCount; + mozilla::TimeStamp mLocationChangeRateLimitSpanStart; +}; + +/** + * Gets a WindowProxy object for a BrowsingContext that lives in a different + * process (creating the object if it doesn't already exist). The WindowProxy + * object will be in the compartment that aCx is currently in. This should only + * be called if aContext doesn't hold a docshell, otherwise the BrowsingContext + * lives in this process, and a same-process WindowProxy should be used (see + * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue + * is the right API to get a WindowProxy for a BrowsingContext. + * + * If aTransplantTo is non-null, then the WindowProxy object will eventually be + * transplanted onto it. Therefore it should be used as the value in the remote + * proxy map. + */ +extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext, + JS::Handle aTransplantTo, + JS::MutableHandle aRetVal); + +using BrowsingContextTransaction = BrowsingContext::BaseTransaction; +using BrowsingContextInitializer = BrowsingContext::IPCInitializer; +using MaybeDiscardedBrowsingContext = MaybeDiscarded; + +// Specialize the transaction object for every translation unit it's used in. +extern template class syncedcontext::Transaction; + +} // namespace dom + +// Allow sending BrowsingContext objects over IPC. +namespace ipc { +template <> +struct IPDLParamTraits> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::MaybeDiscarded& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + dom::MaybeDiscarded* aResult); +}; + +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::BrowsingContext::IPCInitializer& aInitializer); + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + dom::BrowsingContext::IPCInitializer* aInitializer); +}; +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowsingContext_h) diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp new file mode 100644 index 0000000000..66ef235271 --- /dev/null +++ b/docshell/base/BrowsingContextGroup.cpp @@ -0,0 +1,579 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContextGroup.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsFocusManager.h" +#include "nsTHashMap.h" + +namespace mozilla { +namespace dom { + +// Maximum number of successive dialogs before we prompt users to disable +// dialogs for this window. +#define MAX_SUCCESSIVE_DIALOG_COUNT 5 + +static StaticRefPtr sChromeGroup; + +static StaticAutoPtr>> + sBrowsingContextGroups; + +already_AddRefed BrowsingContextGroup::GetOrCreate( + uint64_t aId) { + if (!sBrowsingContextGroups) { + sBrowsingContextGroups = + new nsTHashMap>(); + ClearOnShutdown(&sBrowsingContextGroups); + } + + return do_AddRef(sBrowsingContextGroups->LookupOrInsertWith( + aId, [&aId] { return do_AddRef(new BrowsingContextGroup(aId)); })); +} + +already_AddRefed BrowsingContextGroup::GetExisting( + uint64_t aId) { + if (sBrowsingContextGroups) { + return do_AddRef(sBrowsingContextGroups->Get(aId)); + } + return nullptr; +} + +// Only use 53 bits for the BrowsingContextGroup ID. +static constexpr uint64_t kBrowsingContextGroupIdTotalBits = 53; +static constexpr uint64_t kBrowsingContextGroupIdProcessBits = 22; +static constexpr uint64_t kBrowsingContextGroupIdFlagBits = 1; +static constexpr uint64_t kBrowsingContextGroupIdBits = + kBrowsingContextGroupIdTotalBits - kBrowsingContextGroupIdProcessBits - + kBrowsingContextGroupIdFlagBits; + +// IDs for the relevant flags +static constexpr uint64_t kPotentiallyCrossOriginIsolatedFlag = 0x1; + +// The next ID value which will be used. +static uint64_t sNextBrowsingContextGroupId = 1; + +// Generate the next ID with the given flags. +static uint64_t GenerateBrowsingContextGroupId(uint64_t aFlags) { + MOZ_RELEASE_ASSERT(aFlags < (uint64_t(1) << kBrowsingContextGroupIdFlagBits)); + uint64_t childId = XRE_IsContentProcess() + ? ContentChild::GetSingleton()->GetID() + : uint64_t(0); + MOZ_RELEASE_ASSERT(childId < + (uint64_t(1) << kBrowsingContextGroupIdProcessBits)); + uint64_t id = sNextBrowsingContextGroupId++; + MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kBrowsingContextGroupIdBits)); + + return (childId << (kBrowsingContextGroupIdBits + + kBrowsingContextGroupIdFlagBits)) | + (id << kBrowsingContextGroupIdFlagBits) | aFlags; +} + +// Extract flags from the given ID. +static uint64_t GetBrowsingContextGroupIdFlags(uint64_t aId) { + return aId & ((uint64_t(1) << kBrowsingContextGroupIdFlagBits) - 1); +} + +uint64_t BrowsingContextGroup::CreateId(bool aPotentiallyCrossOriginIsolated) { + // We encode the potentially cross-origin isolated bit within the ID so that + // the information can be recovered whenever the group needs to be re-created + // due to e.g. being garbage-collected. + // + // In the future if we end up needing more complex information stored within + // the ID, we can consider converting it to a more complex type, like a + // string. + uint64_t flags = + aPotentiallyCrossOriginIsolated ? kPotentiallyCrossOriginIsolatedFlag : 0; + uint64_t id = GenerateBrowsingContextGroupId(flags); + MOZ_ASSERT(GetBrowsingContextGroupIdFlags(id) == flags); + return id; +} + +already_AddRefed BrowsingContextGroup::Create( + bool aPotentiallyCrossOriginIsolated) { + return GetOrCreate(CreateId(aPotentiallyCrossOriginIsolated)); +} + +BrowsingContextGroup::BrowsingContextGroup(uint64_t aId) : mId(aId) { + mTimerEventQueue = ThrottledEventQueue::Create( + GetMainThreadSerialEventTarget(), "BrowsingContextGroup timer queue"); + + mWorkerEventQueue = ThrottledEventQueue::Create( + GetMainThreadSerialEventTarget(), "BrowsingContextGroup worker queue"); +} + +void BrowsingContextGroup::Register(nsISupports* aContext) { + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aContext); + mContexts.Insert(aContext); +} + +void BrowsingContextGroup::Unregister(nsISupports* aContext) { + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aContext); + mContexts.Remove(aContext); + + MaybeDestroy(); +} + +void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(this != sChromeGroup, + "cannot have content host for chrome group"); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE, + "cannot use preallocated process as host"); + MOZ_DIAGNOSTIC_ASSERT(!aProcess->GetRemoteType().IsEmpty(), + "host process must have remote type"); + + // XXX: The diagnostic crashes in bug 1816025 seemed to come through caller + // ContentParent::GetNewOrUsedLaunchingBrowserProcess where we already + // did AssertAlive, so IsDead should be irrelevant here. Still it reads + // wrong that we ever might do AddBrowsingContextGroup if aProcess->IsDead(). + if (aProcess->IsDead() || + mHosts.WithEntryHandle(aProcess->GetRemoteType(), [&](auto&& entry) { + if (entry) { + // We know from bug 1816025 that this happens quite often and we have + // bug 1815480 on file that should harden the entire flow. But in the + // meantime we can just live with NOT replacing the found host + // process with a new one here if it is still alive. + MOZ_ASSERT( + entry.Data() == aProcess, + "There's already another host process for this remote type"); + if (!entry.Data()->IsShuttingDown()) { + return false; + } + } + + // This process wasn't already marked as our host, so insert it (or + // update if the old process is shutting down), and begin subscribing, + // unless the process is still launching. + entry.InsertOrUpdate(do_AddRef(aProcess)); + + return true; + })) { + aProcess->AddBrowsingContextGroup(this); + } +} + +void BrowsingContextGroup::RemoveHostProcess(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(aProcess); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + auto entry = mHosts.Lookup(aProcess->GetRemoteType()); + if (entry && entry.Data() == aProcess) { + entry.Remove(); + } +} + +static void CollectContextInitializers( + Span> aContexts, + nsTArray& aInits) { + // The order that we record these initializers is important, as it will keep + // the order that children are attached to their parent in the newly connected + // content process consistent. + for (auto& context : aContexts) { + aInits.AppendElement(context->GetIPCInitializer()); + for (const auto& window : context->GetWindowContexts()) { + aInits.AppendElement(window->GetIPCInitializer()); + CollectContextInitializers(window->Children(), aInits); + } + } +} + +void BrowsingContextGroup::Subscribe(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aProcess && !aProcess->IsLaunching()); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + + // Check if we're already subscribed to this process. + if (!mSubscribers.EnsureInserted(aProcess)) { + return; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // If the process is already marked as dead, we won't be the host, but may + // still need to subscribe to the process due to creating a popup while + // shutting down. + if (!aProcess->IsDead()) { + auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType()); + MOZ_DIAGNOSTIC_ASSERT(hostEntry && hostEntry.Data() == aProcess, + "Cannot subscribe a non-host process"); + } +#endif + + // FIXME: This won't send non-discarded children of discarded BCs, but those + // BCs will be in the process of being destroyed anyway. + // FIXME: Prevent that situation from occuring. + nsTArray inits(mContexts.Count()); + CollectContextInitializers(mToplevels, inits); + + // Send all of our contexts to the target content process. + Unused << aProcess->SendRegisterBrowsingContextGroup(Id(), inits); + + // If the focused or active BrowsingContexts belong in this group, tell the + // newly subscribed process. + if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) { + BrowsingContext* focused = fm->GetFocusedBrowsingContextInChrome(); + if (focused && focused->Group() != this) { + focused = nullptr; + } + BrowsingContext* active = fm->GetActiveBrowsingContextInChrome(); + if (active && active->Group() != this) { + active = nullptr; + } + + if (focused || active) { + Unused << aProcess->SendSetupFocusedAndActive( + focused, fm->GetActionIdForFocusedBrowsingContextInChrome(), active, + fm->GetActionIdForActiveBrowsingContextInChrome()); + } + } +} + +void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(aProcess); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + mSubscribers.Remove(aProcess); + aProcess->RemoveBrowsingContextGroup(this); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType()); + MOZ_DIAGNOSTIC_ASSERT(!hostEntry || hostEntry.Data() != aProcess, + "Unsubscribing existing host entry"); +#endif +} + +ContentParent* BrowsingContextGroup::GetHostProcess( + const nsACString& aRemoteType) { + return mHosts.GetWeak(aRemoteType); +} + +void BrowsingContextGroup::UpdateToplevelsSuspendedIfNeeded() { + if (!StaticPrefs::dom_suspend_inactive_enabled()) { + return; + } + + mToplevelsSuspended = ShouldSuspendAllTopLevelContexts(); + for (const auto& context : mToplevels) { + nsPIDOMWindowOuter* outer = context->GetDOMWindow(); + if (!outer) { + continue; + } + nsCOMPtr inner = outer->GetCurrentInnerWindow(); + if (!inner) { + continue; + } + if (mToplevelsSuspended && !inner->GetWasSuspendedByGroup()) { + inner->Suspend(); + inner->SetWasSuspendedByGroup(true); + } else if (!mToplevelsSuspended && inner->GetWasSuspendedByGroup()) { + inner->Resume(); + inner->SetWasSuspendedByGroup(false); + } + } +} + +bool BrowsingContextGroup::ShouldSuspendAllTopLevelContexts() const { + for (const auto& context : mToplevels) { + if (!context->InactiveForSuspend()) { + return false; + } + } + return true; +} + +BrowsingContextGroup::~BrowsingContextGroup() { Destroy(); } + +void BrowsingContextGroup::Destroy() { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (mDestroyed) { + MOZ_DIAGNOSTIC_ASSERT(mHosts.Count() == 0); + MOZ_DIAGNOSTIC_ASSERT(mSubscribers.Count() == 0); + MOZ_DIAGNOSTIC_ASSERT_IF(sBrowsingContextGroups, + !sBrowsingContextGroups->Contains(Id()) || + *sBrowsingContextGroups->Lookup(Id()) != this); + } + mDestroyed = true; +#endif + + // Make sure to call `RemoveBrowsingContextGroup` for every entry in both + // `mHosts` and `mSubscribers`. This will visit most entries twice, but + // `RemoveBrowsingContextGroup` is safe to call multiple times. + for (const auto& entry : mHosts.Values()) { + entry->RemoveBrowsingContextGroup(this); + } + for (const auto& key : mSubscribers) { + key->RemoveBrowsingContextGroup(this); + } + mHosts.Clear(); + mSubscribers.Clear(); + + if (sBrowsingContextGroups) { + sBrowsingContextGroups->Remove(Id()); + } +} + +void BrowsingContextGroup::AddKeepAlive() { + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + mKeepAliveCount++; +} + +void BrowsingContextGroup::RemoveKeepAlive() { + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0); + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + mKeepAliveCount--; + + MaybeDestroy(); +} + +auto BrowsingContextGroup::MakeKeepAlivePtr() -> KeepAlivePtr { + AddKeepAlive(); + return KeepAlivePtr{do_AddRef(this).take()}; +} + +void BrowsingContextGroup::MaybeDestroy() { + // Once there are no synced contexts referencing a `BrowsingContextGroup`, we + // can clear subscribers and destroy this group. We only do this in the parent + // process, as it will orchestrate destruction of BCGs in content processes. + if (XRE_IsParentProcess() && mContexts.IsEmpty() && mKeepAliveCount == 0 && + this != sChromeGroup) { + Destroy(); + + // We may have been deleted here, as `Destroy()` will clear references. Do + // not access any members at this point. + } +} + +void BrowsingContextGroup::ChildDestroy() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + MOZ_DIAGNOSTIC_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(mContexts.IsEmpty()); + Destroy(); +} + +nsISupports* BrowsingContextGroup::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto); +} + +nsresult BrowsingContextGroup::QueuePostMessageEvent( + already_AddRefed&& aRunnable) { + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) { + if (!mPostMessageEventQueue) { + nsCOMPtr target = GetMainThreadSerialEventTarget(); + mPostMessageEventQueue = ThrottledEventQueue::Create( + target, "PostMessage Queue", + nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS); + nsresult rv = mPostMessageEventQueue->SetIsPaused(false); + MOZ_ALWAYS_SUCCEEDS(rv); + } + + // Ensure the queue is enabled + if (mPostMessageEventQueue->IsPaused()) { + nsresult rv = mPostMessageEventQueue->SetIsPaused(false); + MOZ_ALWAYS_SUCCEEDS(rv); + } + + if (mPostMessageEventQueue) { + mPostMessageEventQueue->Dispatch(std::move(aRunnable), + NS_DISPATCH_NORMAL); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +void BrowsingContextGroup::FlushPostMessageEvents() { + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) { + if (mPostMessageEventQueue) { + nsresult rv = mPostMessageEventQueue->SetIsPaused(true); + MOZ_ALWAYS_SUCCEEDS(rv); + nsCOMPtr event; + while ((event = mPostMessageEventQueue->GetEvent())) { + NS_DispatchToMainThread(event.forget()); + } + } + } +} + +bool BrowsingContextGroup::HasActiveBC() { + for (auto& topLevelBC : Toplevels()) { + if (topLevelBC->IsActive()) { + return true; + } + } + return false; +} + +void BrowsingContextGroup::IncInputEventSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + if (!mHasIncreasedInputTaskManagerSuspensionLevel && HasActiveBC()) { + IncInputTaskManagerSuspensionLevel(); + } + ++mInputEventSuspensionLevel; +} + +void BrowsingContextGroup::DecInputEventSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + --mInputEventSuspensionLevel; + if (!mInputEventSuspensionLevel && + mHasIncreasedInputTaskManagerSuspensionLevel) { + DecInputTaskManagerSuspensionLevel(); + } +} + +void BrowsingContextGroup::DecInputTaskManagerSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + MOZ_ASSERT(mHasIncreasedInputTaskManagerSuspensionLevel); + + InputTaskManager::Get()->DecSuspensionLevel(); + mHasIncreasedInputTaskManagerSuspensionLevel = false; +} + +void BrowsingContextGroup::IncInputTaskManagerSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + MOZ_ASSERT(!mHasIncreasedInputTaskManagerSuspensionLevel); + MOZ_ASSERT(HasActiveBC()); + + InputTaskManager::Get()->IncSuspensionLevel(); + mHasIncreasedInputTaskManagerSuspensionLevel = true; +} + +void BrowsingContextGroup::UpdateInputTaskManagerIfNeeded(bool aIsActive) { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + if (!aIsActive) { + if (mHasIncreasedInputTaskManagerSuspensionLevel) { + MOZ_ASSERT(mInputEventSuspensionLevel > 0); + if (!HasActiveBC()) { + DecInputTaskManagerSuspensionLevel(); + } + } + } else { + if (mInputEventSuspensionLevel && + !mHasIncreasedInputTaskManagerSuspensionLevel) { + IncInputTaskManagerSuspensionLevel(); + } + } +} + +/* static */ +BrowsingContextGroup* BrowsingContextGroup::GetChromeGroup() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + if (!sChromeGroup && XRE_IsParentProcess()) { + sChromeGroup = BrowsingContextGroup::Create(); + ClearOnShutdown(&sChromeGroup); + } + + return sChromeGroup; +} + +void BrowsingContextGroup::GetDocGroups(nsTArray& aDocGroups) { + MOZ_ASSERT(NS_IsMainThread()); + AppendToArray(aDocGroups, mDocGroups.Values()); +} + +already_AddRefed BrowsingContextGroup::AddDocument( + const nsACString& aKey, Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr& docGroup = mDocGroups.LookupOrInsertWith( + aKey, [&] { return DocGroup::Create(this, aKey); }); + + docGroup->AddDocument(aDocument); + return do_AddRef(docGroup); +} + +void BrowsingContextGroup::RemoveDocument(Document* aDocument, + DocGroup* aDocGroup) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr docGroup = aDocGroup; + // Removing the last document in DocGroup might decrement the + // DocGroup BrowsingContextGroup's refcount to 0. + RefPtr kungFuDeathGrip(this); + docGroup->RemoveDocument(aDocument); + + if (docGroup->IsEmpty()) { + mDocGroups.Remove(docGroup->GetKey()); + } +} + +already_AddRefed BrowsingContextGroup::Select( + WindowContext* aParent, BrowsingContext* aOpener) { + if (aParent) { + return do_AddRef(aParent->Group()); + } + if (aOpener) { + return do_AddRef(aOpener->Group()); + } + return Create(); +} + +void BrowsingContextGroup::GetAllGroups( + nsTArray>& aGroups) { + aGroups.Clear(); + if (!sBrowsingContextGroups) { + return; + } + + aGroups = ToArray(sBrowsingContextGroups->Values()); +} + +// For tests only. +void BrowsingContextGroup::ResetDialogAbuseState() { + mDialogAbuseCount = 0; + // Reset the timer. + mLastDialogQuitTime = + TimeStamp::Now() - + TimeDuration::FromSeconds(DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT); +} + +bool BrowsingContextGroup::DialogsAreBeingAbused() { + if (mLastDialogQuitTime.IsNull() || nsContentUtils::IsCallerChrome()) { + return false; + } + + TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime); + if (dialogInterval.ToSeconds() < + Preferences::GetInt("dom.successive_dialog_time_limit", + DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) { + mDialogAbuseCount++; + + return PopupBlocker::GetPopupControlState() > PopupBlocker::openAllowed || + mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT; + } + + // Reset the abuse counter + mDialogAbuseCount = 0; + + return false; +} + +bool BrowsingContextGroup::IsPotentiallyCrossOriginIsolated() { + return GetBrowsingContextGroupIdFlags(mId) & + kPotentiallyCrossOriginIsolatedFlag; +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts, + mToplevels, mHosts, mSubscribers, + mTimerEventQueue, mWorkerEventQueue, + mDocGroups) + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h new file mode 100644 index 0000000000..fb1f2e528c --- /dev/null +++ b/docshell/base/BrowsingContextGroup.h @@ -0,0 +1,318 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContextGroup_h +#define mozilla_dom_BrowsingContextGroup_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/FunctionRef.h" +#include "nsRefPtrHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsTHashSet.h" +#include "nsWrapperCache.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +class ThrottledEventQueue; + +namespace dom { + +// Amount of time allowed between alert/prompt/confirm before enabling +// the stop dialog checkbox. +#define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec + +class BrowsingContext; +class WindowContext; +class ContentParent; +class DocGroup; + +// A BrowsingContextGroup represents the Unit of Related Browsing Contexts in +// the standard. +// +// A BrowsingContext may not hold references to other BrowsingContext objects +// which are not in the same BrowsingContextGroup. +class BrowsingContextGroup final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContextGroup) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(BrowsingContextGroup) + + // Interact with the list of synced contexts. This controls the lifecycle of + // the BrowsingContextGroup and contexts loaded within them. + void Register(nsISupports* aContext); + void Unregister(nsISupports* aContext); + + // Control which processes will be used to host documents loaded in this + // BrowsingContextGroup. There should only ever be one host process per remote + // type. + // + // A new host process will be subscribed to the BrowsingContextGroup unless it + // is still launching, in which case it will subscribe itself when it is done + // launching. + void EnsureHostProcess(ContentParent* aProcess); + + // A removed host process will no longer be used to host documents loaded in + // this BrowsingContextGroup. + void RemoveHostProcess(ContentParent* aProcess); + + // Synchronize the current BrowsingContextGroup state down to the given + // content process, and continue updating it. + // + // You rarely need to call this directly, as it's automatically called by + // |EnsureHostProcess| as needed. + void Subscribe(ContentParent* aProcess); + + // Stop synchronizing the current BrowsingContextGroup state down to a given + // content process. The content process must no longer be a host process. + void Unsubscribe(ContentParent* aProcess); + + // Look up the process which should be used to host documents with this + // RemoteType. This will be a non-dead process associated with this + // BrowsingContextGroup, if possible. + ContentParent* GetHostProcess(const nsACString& aRemoteType); + + // When a BrowsingContext is being discarded, we may want to keep the + // corresponding BrowsingContextGroup alive until the other process + // acknowledges that the BrowsingContext has been discarded. A `KeepAlive` + // will be added to the `BrowsingContextGroup`, delaying destruction. + void AddKeepAlive(); + void RemoveKeepAlive(); + + // A `KeepAlivePtr` will hold both a strong reference to the + // `BrowsingContextGroup` and holds a `KeepAlive`. When the pointer is + // dropped, it will release both the strong reference and the keepalive. + struct KeepAliveDeleter { + void operator()(BrowsingContextGroup* aPtr) { + if (RefPtr ptr = already_AddRefed(aPtr)) { + ptr->RemoveKeepAlive(); + } + } + }; + using KeepAlivePtr = UniquePtr; + KeepAlivePtr MakeKeepAlivePtr(); + + // Call when we want to check if we should suspend or resume all top level + // contexts. + void UpdateToplevelsSuspendedIfNeeded(); + + // Get a reference to the list of toplevel contexts in this + // BrowsingContextGroup. + nsTArray>& Toplevels() { return mToplevels; } + void GetToplevels(nsTArray>& aToplevels) { + aToplevels.AppendElements(mToplevels); + } + + uint64_t Id() { return mId; } + + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Get or create a BrowsingContextGroup with the given ID. + static already_AddRefed GetOrCreate(uint64_t aId); + static already_AddRefed GetExisting(uint64_t aId); + static already_AddRefed Create( + bool aPotentiallyCrossOriginIsolated = false); + static already_AddRefed Select( + WindowContext* aParent, BrowsingContext* aOpener); + + // Like `Create` but only generates and reserves a new ID without actually + // creating the BrowsingContextGroup object. + static uint64_t CreateId(bool aPotentiallyCrossOriginIsolated = false); + + // For each 'ContentParent', except for 'aExcludedParent', + // associated with this group call 'aCallback'. + template + void EachOtherParent(ContentParent* aExcludedParent, Func&& aCallback) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + for (const auto& key : mSubscribers) { + if (key != aExcludedParent) { + aCallback(key); + } + } + } + + // For each 'ContentParent' associated with + // this group call 'aCallback'. + template + void EachParent(Func&& aCallback) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + for (const auto& key : mSubscribers) { + aCallback(key); + } + } + + nsresult QueuePostMessageEvent(already_AddRefed&& aRunnable); + + void FlushPostMessageEvents(); + + // Increase or decrease the suspension level in InputTaskManager + void UpdateInputTaskManagerIfNeeded(bool aIsActive); + + static BrowsingContextGroup* GetChromeGroup(); + + void GetDocGroups(nsTArray& aDocGroups); + + // Called by Document when a Document needs to be added to a DocGroup. + already_AddRefed AddDocument(const nsACString& aKey, + Document* aDocument); + + // Called by Document when a Document needs to be removed from a DocGroup. + // aDocGroup should be from aDocument. This is done to avoid the assert + // in GetDocGroup() which can crash when called during unlinking. + void RemoveDocument(Document* aDocument, DocGroup* aDocGroup); + + mozilla::ThrottledEventQueue* GetTimerEventQueue() const { + return mTimerEventQueue; + } + + mozilla::ThrottledEventQueue* GetWorkerEventQueue() const { + return mWorkerEventQueue; + } + + void SetAreDialogsEnabled(bool aAreDialogsEnabled) { + mAreDialogsEnabled = aAreDialogsEnabled; + } + + bool GetAreDialogsEnabled() { return mAreDialogsEnabled; } + + bool GetDialogAbuseCount() { return mDialogAbuseCount; } + + // For tests only. + void ResetDialogAbuseState(); + + bool DialogsAreBeingAbused(); + + TimeStamp GetLastDialogQuitTime() { return mLastDialogQuitTime; } + + void SetLastDialogQuitTime(TimeStamp aLastDialogQuitTime) { + mLastDialogQuitTime = aLastDialogQuitTime; + } + + // Whether all toplevel documents loaded in this group are allowed to be + // Cross-Origin Isolated. + // + // This does not reflect the actual value of `crossOriginIsolated`, as that + // also requires that the document is loaded within a `webCOOP+COEP` content + // process. + bool IsPotentiallyCrossOriginIsolated(); + + static void GetAllGroups(nsTArray>& aGroups); + + void IncInputEventSuspensionLevel(); + void DecInputEventSuspensionLevel(); + + void ChildDestroy(); + + private: + friend class CanonicalBrowsingContext; + + explicit BrowsingContextGroup(uint64_t aId); + ~BrowsingContextGroup(); + + void MaybeDestroy(); + void Destroy(); + + bool ShouldSuspendAllTopLevelContexts() const; + + bool HasActiveBC(); + void DecInputTaskManagerSuspensionLevel(); + void IncInputTaskManagerSuspensionLevel(); + + uint64_t mId; + + uint32_t mKeepAliveCount = 0; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool mDestroyed = false; +#endif + + // A BrowsingContextGroup contains a series of {Browsing,Window}Context + // objects. They are addressed using a hashtable to avoid linear lookup when + // adding or removing elements from the set. + // + // FIXME: This list is only required over a counter to keep nested + // non-discarded contexts within discarded contexts alive. It should be + // removed in the future. + // FIXME: Consider introducing a better common base than `nsISupports`? + nsTHashSet> mContexts; + + // The set of toplevel browsing contexts in the current BrowsingContextGroup. + nsTArray> mToplevels; + + // Whether or not all toplevels in this group should be suspended + bool mToplevelsSuspended = false; + + // DocGroups are thread-safe, and not able to be cycle collected, + // but we still keep strong pointers. When all Documents are removed + // from DocGroup (by the BrowsingContextGroup), the DocGroup is + // removed from here. + nsRefPtrHashtable mDocGroups; + + // The content process which will host documents in this BrowsingContextGroup + // which need to be loaded with a given remote type. + // + // A non-launching host process must also be a subscriber, though a launching + // host process may not yet be subscribed, and a subscriber need not be a host + // process. + nsRefPtrHashtable mHosts; + + nsTHashSet> mSubscribers; + + // A queue to store postMessage events during page load, the queue will be + // flushed once the page is loaded + RefPtr mPostMessageEventQueue; + + RefPtr mTimerEventQueue; + RefPtr mWorkerEventQueue; + + // A counter to keep track of the input event suspension level of this BCG + // + // We use BrowsingContextGroup to emulate process isolation in Fission, so + // documents within the same the same BCG will behave like they share + // the same input task queue. + uint32_t mInputEventSuspensionLevel = 0; + // Whether this BCG has increased the suspension level in InputTaskManager + bool mHasIncreasedInputTaskManagerSuspensionLevel = false; + + // This flag keeps track of whether dialogs are + // currently enabled for windows of this group. + // It's OK to have these local to each process only because even if + // frames from two/three different sites (and thus, processes) coordinate a + // dialog abuse attack, they would only the double/triple number of dialogs, + // as it is still limited per-site. + bool mAreDialogsEnabled = true; + + // This counts the number of windows that have been opened in rapid succession + // (i.e. within dom.successive_dialog_time_limit of each other). It is reset + // to 0 once a dialog is opened after dom.successive_dialog_time_limit seconds + // have elapsed without any other dialogs. + // See comment for mAreDialogsEnabled as to why it's ok to have this local to + // each process. + uint32_t mDialogAbuseCount = 0; + + // This holds the time when the last modal dialog was shown. If more than + // MAX_DIALOG_LIMIT dialogs are shown within the time span defined by + // dom.successive_dialog_time_limit, we show a checkbox or confirmation prompt + // to allow disabling of further dialogs from windows in this BC group. + TimeStamp mLastDialogQuitTime; +}; +} // namespace dom +} // namespace mozilla + +inline void ImplCycleCollectionUnlink( + mozilla::dom::BrowsingContextGroup::KeepAlivePtr& aField) { + aField = nullptr; +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + mozilla::dom::BrowsingContextGroup::KeepAlivePtr& aField, const char* aName, + uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +#endif // !defined(mozilla_dom_BrowsingContextGroup_h) diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp new file mode 100644 index 0000000000..2c6baf4fa8 --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.cpp @@ -0,0 +1,432 @@ +/* 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/. */ + +#include "BrowsingContextWebProgress.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "nsCOMPtr.h" +#include "nsIWebProgressListener.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "nsIChannel.h" +#include "xptinfo.h" + +namespace mozilla { +namespace dom { + +static mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress"); + +static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext); +static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress); +static nsCString DescribeRequest(nsIRequest* aRequest); +static nsCString DescribeWebProgressFlags(uint32_t aFlags, + const nsACString& aPrefix); +static nsCString DescribeError(nsresult aError); + +NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END + +BrowsingContextWebProgress::BrowsingContextWebProgress( + CanonicalBrowsingContext* aBrowsingContext) + : mCurrentBrowsingContext(aBrowsingContext) {} + +BrowsingContextWebProgress::~BrowsingContextWebProgress() = default; + +NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener( + nsIWebProgressListener* aListener, uint32_t aNotifyMask) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + if (mListenerInfoList.Contains(listener)) { + // The listener is already registered! + return NS_ERROR_FAILURE; + } + + mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask)); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener( + nsIWebProgressListener* aListener) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM( + BrowsingContext** aBrowsingContext) { + NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext); + return NS_OK; +} + +BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() { + return mCurrentBrowsingContext; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow( + mozIDOMWindowProxy** aDOMWindow) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) { + *aIsTopLevel = mCurrentBrowsingContext->IsTop(); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument( + bool* aIsLoadingDocument) { + *aIsLoadingDocument = mIsLoadingDocument; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) { + *aLoadType = mLoadType; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BrowsingContextWebProgress::UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function& aCallback) { + RefPtr kungFuDeathGrip = this; + + ListenerArray::ForwardIterator iter(mListenerInfoList); + while (iter.HasMore()) { + ListenerInfo& info = iter.GetNext(); + if (!(info.mNotifyMask & aFlag)) { + continue; + } + + nsCOMPtr listener = + do_QueryReferent(info.mWeakListener); + if (!listener) { + mListenerInfoList.RemoveElement(info); + continue; + } + + aCallback(listener); + } + + mListenerInfoList.Compact(); + + // Notify the parent BrowsingContextWebProgress of the event to continue + // propagating. + auto* parent = mCurrentBrowsingContext->GetParent(); + if (parent && parent->GetWebProgress()) { + aCallback(parent->GetWebProgress()); + } +} + +void BrowsingContextWebProgress::ContextDiscarded() { + if (!mIsLoadingDocument) { + return; + } + + // If our BrowsingContext is being discarded while still loading a document, + // fire a synthetic `STATE_STOP` to end the ongoing load. + MOZ_LOG(gBCWebProgressLog, LogLevel::Info, + ("Discarded while loading %s", + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + + // This matches what nsDocLoader::doStopDocumentLoad does, except we don't + // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`, + // nsBrowserStatusFilter would filter it out before it gets to the parent + // process. + nsCOMPtr request = mLoadingDocumentRequest; + OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK, + NS_ERROR_ABORT); +} + +void BrowsingContextWebProgress::ContextReplaced( + CanonicalBrowsingContext* aNewContext) { + mCurrentBrowsingContext = aNewContext; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnStateChange(%s, %s, %s, %s) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(), + DescribeError(aStatus).get(), + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + + bool targetIsThis = aWebProgress == this; + + // We may receive a request from an in-process nsDocShell which doesn't have + // `aWebProgress == this` which we should still consider as targeting + // ourselves. + if (nsCOMPtr docShell = do_QueryInterface(aWebProgress); + docShell && docShell->GetBrowsingContext() == mCurrentBrowsingContext) { + targetIsThis = true; + aWebProgress->GetLoadType(&mLoadType); + } + + // Track `mIsLoadingDocument` based on the notifications we've received so far + // if the nsIWebProgress being targeted is this one. + if (targetIsThis) { + constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_DOCUMENT | + nsIWebProgressListener::STATE_IS_REQUEST | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK; + constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_WINDOW; + constexpr uint32_t redirectFlags = + nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT; + if ((aStateFlags & startFlags) == startFlags) { + if (mIsLoadingDocument) { + // We received a duplicate `STATE_START` notification, silence this + // notification until we receive the matching `STATE_STOP` to not fire + // duplicate `STATE_START` notifications into frontend on process + // switches. + return NS_OK; + } + mIsLoadingDocument = true; + + // Record the request we started the load with, so we can emit a synthetic + // `STATE_STOP` notification if the BrowsingContext is discarded before + // the notification arrives. + mLoadingDocumentRequest = aRequest; + } else if ((aStateFlags & stopFlags) == stopFlags) { + // We've received a `STATE_STOP` notification targeting this web progress, + // clear our loading document flag. + mIsLoadingDocument = false; + mLoadingDocumentRequest = nullptr; + } else if (mIsLoadingDocument && + (aStateFlags & redirectFlags) == redirectFlags) { + // If we see a redirected document load, update the loading request which + // we'll emit the synthetic STATE_STOP notification with. + mLoadingDocumentRequest = aRequest; + } + } + + UpdateAndNotifyListeners( + ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL), + [&](nsIWebProgressListener* listener) { + listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) { + listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnLocationChange(%s, %s, %s, %s) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aLocation ? aLocation->GetSpecOrDefault().get() : "", + DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(), + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) { + listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnStatusChange(%s, %s, %s, \"%s\") on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(), + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) { + listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnSecurityChange(%s, %s, %x) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aState, DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) { + listener->OnSecurityChange(aWebProgress, aRequest, aState); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnContentBlockingEvent(%s, %s, %x) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING, + [&](nsIWebProgressListener* listener) { + listener->OnContentBlockingEvent(aWebProgress, + aRequest, aEvent); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) { + NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods for notification logging + +static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) { + if (!aContext) { + return ""_ns; + } + + nsCOMPtr currentURI = aContext->GetCurrentURI(); + return nsPrintfCString( + "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(), + currentURI ? currentURI->GetSpecOrDefault().get() : ""); +} + +static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) { + if (!aWebProgress) { + return ""_ns; + } + + bool isTopLevel = false; + aWebProgress->GetIsTopLevel(&isTopLevel); + bool isLoadingDocument = false; + aWebProgress->GetIsLoadingDocument(&isLoadingDocument); + return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel, + isLoadingDocument); +} + +static nsCString DescribeRequest(nsIRequest* aRequest) { + if (!aRequest) { + return ""_ns; + } + + nsCOMPtr channel = do_QueryInterface(aRequest); + if (!channel) { + return ""_ns; + } + + nsCOMPtr originalURI; + channel->GetOriginalURI(getter_AddRefs(originalURI)); + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + + return nsPrintfCString( + "{URI:%s, originalURI:%s}", + uri ? uri->GetSpecOrDefault().get() : "", + originalURI ? originalURI->GetSpecOrDefault().get() : ""); +} + +static nsCString DescribeWebProgressFlags(uint32_t aFlags, + const nsACString& aPrefix) { + nsCString flags; + uint32_t remaining = aFlags; + + // Hackily fetch the names of each constant from the XPT information used for + // reflecting it into JS. This doesn't need to be reliable and just exists as + // a logging aid. + // + // If a change to xpt in the future breaks this code, just delete it and + // replace it with a normal hex log. + if (const auto* ifaceInfo = + nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) { + for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) { + const auto& constInfo = ifaceInfo->Constant(i); + nsDependentCString name(constInfo.Name()); + if (!StringBeginsWith(name, aPrefix)) { + continue; + } + + if (remaining & constInfo.mValue) { + remaining &= ~constInfo.mValue; + if (!flags.IsEmpty()) { + flags.AppendLiteral("|"); + } + flags.Append(name); + } + } + } + if (remaining != 0 || flags.IsEmpty()) { + if (!flags.IsEmpty()) { + flags.AppendLiteral("|"); + } + flags.AppendInt(remaining, 16); + } + return flags; +} + +static nsCString DescribeError(nsresult aError) { + nsCString name; + GetErrorName(aError, name); + return name; +} + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/BrowsingContextWebProgress.h b/docshell/base/BrowsingContextWebProgress.h new file mode 100644 index 0000000000..b39fb2545d --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.h @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContextWebProgress_h +#define mozilla_dom_BrowsingContextWebProgress_h + +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" +#include "nsCycleCollectionParticipant.h" + +namespace mozilla::dom { + +class CanonicalBrowsingContext; + +/// Object acting as the nsIWebProgress instance for a BrowsingContext over its +/// lifetime. +/// +/// An active toplevel CanonicalBrowsingContext will always have a +/// BrowsingContextWebProgress, which will be moved between contexts as +/// BrowsingContextGroup-changing loads are performed. +/// +/// Subframes will only have a `BrowsingContextWebProgress` if they are loaded +/// in a content process, and will use the nsDocShell instead if they are loaded +/// in the parent process, as parent process documents cannot have or be +/// out-of-process iframes. +class BrowsingContextWebProgress final : public nsIWebProgress, + public nsIWebProgressListener { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(BrowsingContextWebProgress, + nsIWebProgress) + NS_DECL_NSIWEBPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + + explicit BrowsingContextWebProgress( + CanonicalBrowsingContext* aBrowsingContext); + + struct ListenerInfo { + ListenerInfo(nsIWeakReference* aListener, unsigned long aNotifyMask) + : mWeakListener(aListener), mNotifyMask(aNotifyMask) {} + + bool operator==(const ListenerInfo& aOther) const { + return mWeakListener == aOther.mWeakListener; + } + bool operator==(const nsWeakPtr& aOther) const { + return mWeakListener == aOther; + } + + // Weak pointer for the nsIWebProgressListener... + nsWeakPtr mWeakListener; + + // Mask indicating which notifications the listener wants to receive. + unsigned long mNotifyMask; + }; + + void ContextDiscarded(); + void ContextReplaced(CanonicalBrowsingContext* aNewContext); + + void SetLoadType(uint32_t aLoadType) { mLoadType = aLoadType; } + + private: + virtual ~BrowsingContextWebProgress(); + + void UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function& aCallback); + + using ListenerArray = nsAutoTObserverArray; + ListenerArray mListenerInfoList; + + // The current BrowsingContext which owns this BrowsingContextWebProgress. + // This context may change during navigations and may not be fully attached at + // all times. + RefPtr mCurrentBrowsingContext; + + // The current request being actively loaded by the BrowsingContext. Only set + // while mIsLoadingDocument is true, and is used to fire STATE_STOP + // notifications if the BrowsingContext is discarded while the load is + // ongoing. + nsCOMPtr mLoadingDocumentRequest; + + // The most recent load type observed for this BrowsingContextWebProgress. + uint32_t mLoadType = 0; + + // Are we currently in the process of loading a document? This is true between + // the `STATE_START` notification from content and the `STATE_STOP` + // notification being received. Duplicate `STATE_START` events may be + // discarded while loading a document to avoid noise caused by process + // switches. + bool mIsLoadingDocument = false; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_BrowsingContextWebProgress_h diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp new file mode 100644 index 0000000000..b6bedf7ba9 --- /dev/null +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -0,0 +1,3069 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CanonicalBrowsingContext.h" + +#include "ErrorList.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/EventForwards.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/dom/PBackgroundSessionStorageCache.h" +#include "mozilla/dom/PWindowGlobalParent.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/MediaController.h" +#include "mozilla/dom/MediaControlService.h" +#include "mozilla/dom/ContentPlaybackController.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/ipc/ProtocolUtils.h" +#ifdef NS_PRINTING +# include "mozilla/layout/RemotePrintJobParent.h" +#endif +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_docshell.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/Telemetry.h" +#include "nsILayoutHistoryState.h" +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsService.h" +#include "nsISupports.h" +#include "nsIWebNavigation.h" +#include "mozilla/MozPromiseInlines.h" +#include "nsDocShell.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsGlobalWindowOuter.h" +#include "nsIWebBrowserChrome.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsSHistory.h" +#include "nsSecureBrowserUI.h" +#include "nsQueryObject.h" +#include "nsBrowserStatusFilter.h" +#include "nsIBrowser.h" +#include "nsTHashSet.h" +#include "SessionStoreFunctions.h" +#include "nsIXPConnect.h" +#include "nsImportModule.h" +#include "UnitTransforms.h" + +using namespace mozilla::ipc; + +extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gSHLog; +extern mozilla::LazyLogModule gSHIPBFCacheLog; + +#define AUTOPLAY_LOG(msg, ...) \ + MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +static mozilla::LazyLogModule sPBContext("PBContext"); + +// Global count of canonical browsing contexts with the private attribute set +static uint32_t gNumberOfPrivateContexts = 0; + +// Current parent process epoch for parent initiated navigations +static uint64_t gParentInitiatedNavigationEpoch = 0; + +static void IncreasePrivateCount() { + gNumberOfPrivateContexts++; + MOZ_LOG(sPBContext, mozilla::LogLevel::Debug, + ("%s: Private browsing context count %d -> %d", __func__, + gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts)); + if (gNumberOfPrivateContexts > 1) { + return; + } + + static bool sHasSeenPrivateContext = false; + if (!sHasSeenPrivateContext) { + sHasSeenPrivateContext = true; + mozilla::Telemetry::ScalarSet( + mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED, + true); + } +} + +static void DecreasePrivateCount() { + MOZ_ASSERT(gNumberOfPrivateContexts > 0); + gNumberOfPrivateContexts--; + + MOZ_LOG(sPBContext, mozilla::LogLevel::Debug, + ("%s: Private browsing context count %d -> %d", __func__, + gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts)); + if (!gNumberOfPrivateContexts && + !mozilla::StaticPrefs::browser_privatebrowsing_autostart()) { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + MOZ_LOG(sPBContext, mozilla::LogLevel::Debug, + ("%s: last-pb-context-exited fired", __func__)); + observerService->NotifyObservers(nullptr, "last-pb-context-exited", + nullptr); + } + } +} + +namespace mozilla { +namespace dom { + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, + uint64_t aOwnerProcessId, + uint64_t aEmbedderProcessId, + BrowsingContext::Type aType, + FieldValues&& aInit) + : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType, + std::move(aInit)), + mProcessId(aOwnerProcessId), + mEmbedderProcessId(aEmbedderProcessId), + mPermanentKey(JS::NullValue()) { + // You are only ever allowed to create CanonicalBrowsingContexts in the + // parent process. + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + + // The initial URI in a BrowsingContext is always "about:blank". + MOZ_ALWAYS_SUCCEEDS( + NS_NewURI(getter_AddRefs(mCurrentRemoteURI), "about:blank")); + + mozilla::HoldJSObjects(this); +} + +CanonicalBrowsingContext::~CanonicalBrowsingContext() { + mPermanentKey.setNull(); + + mozilla::DropJSObjects(this); + + if (mSessionHistory) { + mSessionHistory->SetBrowsingContext(nullptr); + } +} + +/* static */ +already_AddRefed CanonicalBrowsingContext::Get( + uint64_t aId) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return BrowsingContext::Get(aId).downcast(); +} + +/* static */ +CanonicalBrowsingContext* CanonicalBrowsingContext::Cast( + BrowsingContext* aContext) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return static_cast(aContext); +} + +/* static */ +const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast( + const BrowsingContext* aContext) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return static_cast(aContext); +} + +already_AddRefed CanonicalBrowsingContext::Cast( + already_AddRefed&& aContext) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return aContext.downcast(); +} + +ContentParent* CanonicalBrowsingContext::GetContentParent() const { + if (mProcessId == 0) { + return nullptr; + } + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (!cpm) { + return nullptr; + } + return cpm->GetContentProcessById(ContentParentId(mProcessId)); +} + +void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType, + ErrorResult& aRv) const { + // If we're in the parent process, dump out the void string. + if (mProcessId == 0) { + aRemoteType = NOT_REMOTE_TYPE; + return; + } + + ContentParent* cp = GetContentParent(); + if (!cp) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + aRemoteType = cp->GetRemoteType(); +} + +void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64 + " -> 0x%08" PRIx64 ")", + Id(), mProcessId, aProcessId)); + + mProcessId = aProcessId; +} + +nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() { + if (!IsTop()) { + return nullptr; + } + if (!mSecureBrowserUI) { + mSecureBrowserUI = new nsSecureBrowserUI(this); + } + return mSecureBrowserUI; +} + +namespace { +// The DocShellProgressBridge is attached to a root content docshell loaded in +// the parent process. Notifications are paired up with the docshell which they +// came from, so that they can be fired to the correct +// BrowsingContextWebProgress and bubble through this tree separately. +// +// Notifications are filtered by a nsBrowserStatusFilter before being received +// by the DocShellProgressBridge. +class DocShellProgressBridge : public nsIWebProgressListener { + public: + NS_DECL_ISUPPORTS + // NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener + // methods accepting an `aWebProgress` argument. If this changes in the + // future, this may need to be written manually. + NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress)) + + explicit DocShellProgressBridge(uint64_t aTopContextId) + : mTopContextId(aTopContextId) {} + + private: + virtual ~DocShellProgressBridge() = default; + + nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) { + RefPtr context; + if (nsCOMPtr docShell = do_QueryInterface(aWebProgress)) { + context = docShell->GetBrowsingContext()->Canonical(); + } else { + context = CanonicalBrowsingContext::Get(mTopContextId); + } + return context && !context->IsDiscarded() ? context->GetWebProgress() + : nullptr; + } + + uint64_t mTopContextId = 0; +}; + +NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener) +} // namespace + +void CanonicalBrowsingContext::MaybeAddAsProgressListener( + nsIWebProgress* aWebProgress) { + // Only add as a listener if the created docshell is a toplevel content + // docshell. We'll get notifications for all of our subframes through a single + // listener. + if (!IsTopContent()) { + return; + } + + if (!mDocShellProgressBridge) { + mDocShellProgressBridge = new DocShellProgressBridge(Id()); + mStatusFilter = new nsBrowserStatusFilter(); + mStatusFilter->AddProgressListener(mDocShellProgressBridge, + nsIWebProgress::NOTIFY_ALL); + } + + aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL); +} + +void CanonicalBrowsingContext::ReplacedBy( + CanonicalBrowsingContext* aNewContext, + const NavigationIsolationOptions& aRemotenessOptions) { + MOZ_ASSERT(!aNewContext->mWebProgress); + MOZ_ASSERT(!aNewContext->mSessionHistory); + MOZ_ASSERT(IsTop() && aNewContext->IsTop()); + + mIsReplaced = true; + aNewContext->mIsReplaced = false; + + if (mStatusFilter) { + mStatusFilter->RemoveProgressListener(mDocShellProgressBridge); + mStatusFilter = nullptr; + } + + mWebProgress->ContextReplaced(aNewContext); + aNewContext->mWebProgress = std::move(mWebProgress); + + // Use the Transaction for the fields which need to be updated whether or not + // the new context has been attached before. + // SetWithoutSyncing can be used if context hasn't been attached. + Transaction txn; + txn.SetBrowserId(GetBrowserId()); + txn.SetIsAppTab(GetIsAppTab()); + txn.SetHasSiblings(GetHasSiblings()); + txn.SetHistoryID(GetHistoryID()); + txn.SetExplicitActive(GetExplicitActive()); + txn.SetEmbedderColorSchemes(GetEmbedderColorSchemes()); + txn.SetHasRestoreData(GetHasRestoreData()); + txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart()); + + // Propagate some settings on BrowsingContext replacement so they're not lost + // on bfcached navigations. These are important for GeckoView (see bug + // 1781936). + txn.SetAllowJavascript(GetAllowJavascript()); + txn.SetForceEnableTrackingProtection(GetForceEnableTrackingProtection()); + txn.SetUserAgentOverride(GetUserAgentOverride()); + txn.SetSuspendMediaWhenInactive(GetSuspendMediaWhenInactive()); + txn.SetDisplayMode(GetDisplayMode()); + txn.SetForceDesktopViewport(GetForceDesktopViewport()); + txn.SetIsUnderHiddenEmbedderElement(GetIsUnderHiddenEmbedderElement()); + + // Propagate the default load flags so that the TRR mode flags are forwarded + // to the new browsing context. See bug 1828643. + txn.SetDefaultLoadFlags(GetDefaultLoadFlags()); + + // As this is a different BrowsingContext, set InitialSandboxFlags to the + // current flags in the new context so that they also apply to any initial + // about:blank documents created in it. + txn.SetSandboxFlags(GetSandboxFlags()); + txn.SetInitialSandboxFlags(GetSandboxFlags()); + txn.SetTargetTopLevelLinkClicksToBlankInternal( + TargetTopLevelLinkClicksToBlank()); + if (aNewContext->EverAttached()) { + MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext)); + } else { + txn.CommitWithoutSyncing(aNewContext); + } + + aNewContext->mRestoreState = mRestoreState.forget(); + MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false)); + + // XXXBFCache name handling is still a bit broken in Fission in general, + // at least in case name should be cleared. + if (aRemotenessOptions.mTryUseBFCache) { + MOZ_ASSERT(!aNewContext->EverAttached()); + aNewContext->mFields.SetWithoutSyncing(GetName()); + // We don't copy over HasLoadedNonInitialDocument here, we'll actually end + // up loading a new initial document at this point, before the real load. + // The real load will then end up setting HasLoadedNonInitialDocument to + // true. + } + + if (mSessionHistory) { + mSessionHistory->SetBrowsingContext(aNewContext); + // At this point we will be creating a new ChildSHistory in the child. + // That means that the child's epoch will be reset, so it makes sense to + // reset the epoch in the parent too. + mSessionHistory->SetEpoch(0, Nothing()); + mSessionHistory.swap(aNewContext->mSessionHistory); + RefPtr childSHistory = ForgetChildSHistory(); + aNewContext->SetChildSHistory(childSHistory); + } + + if (mozilla::SessionHistoryInParent()) { + BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id()); + } + + // Transfer the ownership of the priority active status from the old context + // to the new context. + aNewContext->mPriorityActive = mPriorityActive; + mPriorityActive = false; + + MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty()); + mLoadingEntries.SwapElements(aNewContext->mLoadingEntries); + MOZ_ASSERT(!aNewContext->mActiveEntry); + mActiveEntry.swap(aNewContext->mActiveEntry); + + aNewContext->mPermanentKey = mPermanentKey; + mPermanentKey.setNull(); +} + +void CanonicalBrowsingContext::UpdateSecurityState() { + if (mSecureBrowserUI) { + mSecureBrowserUI->RecomputeSecurityFlags(); + } +} + +void CanonicalBrowsingContext::GetWindowGlobals( + nsTArray>& aWindows) { + aWindows.SetCapacity(GetWindowContexts().Length()); + for (auto& window : GetWindowContexts()) { + aWindows.AppendElement(static_cast(window.get())); + } +} + +WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const { + return static_cast(GetCurrentWindowContext()); +} + +WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() { + return static_cast( + BrowsingContext::GetParentWindowContext()); +} + +WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() { + return static_cast( + BrowsingContext::GetTopWindowContext()); +} + +already_AddRefed +CanonicalBrowsingContext::GetParentProcessWidgetContaining() { + // If our document is loaded in-process, such as chrome documents, get the + // widget directly from our outer window. Otherwise, try to get the widget + // from the toplevel content's browser's element. + nsCOMPtr widget; + if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) { + widget = window->GetNearestWidget(); + } else if (Element* topEmbedder = Top()->GetEmbedderElement()) { + widget = nsContentUtils::WidgetForContent(topEmbedder); + if (!widget) { + widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc()); + } + } + + if (widget) { + widget = widget->GetTopLevelWidget(); + } + + return widget.forget(); +} + +already_AddRefed +CanonicalBrowsingContext::GetBrowserDOMWindow() { + RefPtr chromeTop = TopCrossChromeBoundary(); + if (nsCOMPtr chromeWin = + do_QueryInterface(chromeTop->GetDOMWindow())) { + nsCOMPtr bdw; + if (NS_SUCCEEDED(chromeWin->GetBrowserDOMWindow(getter_AddRefs(bdw)))) { + return bdw.forget(); + } + } + return nullptr; +} + +already_AddRefed +CanonicalBrowsingContext::GetEmbedderWindowGlobal() const { + uint64_t windowId = GetEmbedderInnerWindowId(); + if (windowId == 0) { + return nullptr; + } + + return WindowGlobalParent::GetByInnerWindowId(windowId); +} + +already_AddRefed +CanonicalBrowsingContext::GetParentCrossChromeBoundary() { + if (GetParent()) { + return do_AddRef(Cast(GetParent())); + } + if (GetEmbedderElement()) { + return do_AddRef( + Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext())); + } + return nullptr; +} + +already_AddRefed +CanonicalBrowsingContext::TopCrossChromeBoundary() { + RefPtr bc(this); + while (RefPtr parent = + bc->GetParentCrossChromeBoundary()) { + bc = parent.forget(); + } + return bc.forget(); +} + +Nullable CanonicalBrowsingContext::GetTopChromeWindow() { + RefPtr bc = TopCrossChromeBoundary(); + if (bc->IsChrome()) { + return WindowProxyHolder(bc.forget()); + } + return nullptr; +} + +nsISHistory* CanonicalBrowsingContext::GetSessionHistory() { + if (!IsTop()) { + return Cast(Top())->GetSessionHistory(); + } + + // Check GetChildSessionHistory() to make sure that this BrowsingContext has + // session history enabled. + if (!mSessionHistory && GetChildSessionHistory()) { + mSessionHistory = new nsSHistory(this); + } + + return mSessionHistory; +} + +SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() { + return mActiveEntry; +} + +void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( + SessionHistoryEntry* aEntry) { + mActiveEntry = aEntry; +} + +bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) { + // XXX Should we check also loading entries? + return aEntry && mActiveEntry == aEntry; +} + +void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry, + nsISHEntry* aNewEntry) { + // XXX Should we check also loading entries? + if (mActiveEntry == aOldEntry) { + nsCOMPtr newEntry = do_QueryInterface(aNewEntry); + mActiveEntry = newEntry.forget(); + } +} + +void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry( + uint64_t aLoadId, SessionHistoryEntry* aEntry) { + Unused << SetHistoryID(aEntry->DocshellID()); + mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry}); +} + +void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent( + Maybe& aLoadingInfo) { + nsISHistory* shistory = GetSessionHistory(); + if (!shistory || !GetParent()) { + return; + } + + SessionHistoryEntry* parentSHE = + GetParent()->Canonical()->GetActiveSessionHistoryEntry(); + if (parentSHE) { + int32_t index = -1; + for (BrowsingContext* sibling : GetParent()->Children()) { + ++index; + if (sibling == this) { + nsCOMPtr shEntry; + parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild( + index, getter_AddRefs(shEntry)); + nsCOMPtr she = do_QueryInterface(shEntry); + if (she) { + aLoadingInfo.emplace(she); + mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{ + aLoadingInfo.value().mLoadId, she.get()}); + Unused << SetHistoryID(she->DocshellID()); + } + break; + } + } + } +} + +UniquePtr +CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad( + nsDocShellLoadState* aLoadState, SessionHistoryEntry* existingEntry, + nsIChannel* aChannel) { + RefPtr entry; + const LoadingSessionHistoryInfo* existingLoadingInfo = + aLoadState->GetLoadingSessionHistoryInfo(); + MOZ_ASSERT_IF(!existingLoadingInfo, !existingEntry); + if (existingLoadingInfo) { + if (existingEntry) { + entry = existingEntry; + } else { + MOZ_ASSERT(!existingLoadingInfo->mLoadIsFromSessionHistory); + + SessionHistoryEntry::LoadingEntry* loadingEntry = + SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p", + existingLoadingInfo->mLoadId, entry.get())); + if (!loadingEntry) { + return nullptr; + } + entry = loadingEntry->mEntry; + } + + // If the entry was updated, update also the LoadingSessionHistoryInfo. + UniquePtr lshi = + MakeUnique(entry, existingLoadingInfo); + aLoadState->SetLoadingSessionHistoryInfo(std::move(lshi)); + existingLoadingInfo = aLoadState->GetLoadingSessionHistoryInfo(); + Unused << SetHistoryEntryCount(entry->BCHistoryLength()); + } else if (aLoadState->LoadType() == LOAD_REFRESH && + !ShouldAddEntryForRefresh(aLoadState->URI(), + aLoadState->PostDataStream()) && + mActiveEntry) { + entry = mActiveEntry; + } else { + entry = new SessionHistoryEntry(aLoadState, aChannel); + if (IsTop()) { + // Only top level pages care about Get/SetPersist. + entry->SetPersist( + nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel)); + } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) { + entry->SetIsSubFrame(true); + } + entry->SetDocshellID(GetHistoryID()); + entry->SetIsDynamicallyAdded(CreatedDynamically()); + entry->SetForInitialLoad(true); + } + MOZ_DIAGNOSTIC_ASSERT(entry); + + UniquePtr loadingInfo; + if (existingLoadingInfo) { + loadingInfo = MakeUnique(*existingLoadingInfo); + } else { + loadingInfo = MakeUnique(entry); + mLoadingEntries.AppendElement( + LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry}); + } + + MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId)->mEntry == + entry); + + return loadingInfo; +} + +UniquePtr +CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad( + LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel) { + MOZ_ASSERT(aInfo); + MOZ_ASSERT(aNewChannel); + + SessionHistoryInfo newInfo = SessionHistoryInfo( + aNewChannel, aInfo->mInfo.LoadType(), + aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp()); + + for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { + if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) { + RefPtr loadingEntry = mLoadingEntries[i].mEntry; + loadingEntry->SetInfo(&newInfo); + + if (IsTop()) { + // Only top level pages care about Get/SetPersist. + nsCOMPtr uri; + aNewChannel->GetURI(getter_AddRefs(uri)); + loadingEntry->SetPersist( + nsDocShell::ShouldAddToSessionHistory(uri, aNewChannel)); + } else { + loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame()); + } + loadingEntry->SetDocshellID(GetHistoryID()); + loadingEntry->SetIsDynamicallyAdded(CreatedDynamically()); + return MakeUnique(loadingEntry, aInfo); + } + } + return nullptr; +} + +using PrintPromise = CanonicalBrowsingContext::PrintPromise; +#ifdef NS_PRINTING +class PrintListenerAdapter final : public nsIWebProgressListener { + public: + explicit PrintListenerAdapter(PrintPromise::Private* aPromise) + : mPromise(aPromise) {} + + NS_DECL_ISUPPORTS + + // NS_DECL_NSIWEBPROGRESSLISTENER + NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) override { + if (aStateFlags & nsIWebProgressListener::STATE_STOP && + aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) { + mPromise->Resolve(true, __func__); + mPromise = nullptr; + } + return NS_OK; + } + NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) override { + if (aStatus != NS_OK && mPromise) { + mPromise->Reject(aStatus, __func__); + mPromise = nullptr; + } + return NS_OK; + } + NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) override { + return NS_OK; + } + NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, nsIURI* aLocation, + uint32_t aFlags) override { + return NS_OK; + } + NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aState) override { + return NS_OK; + } + NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) override { + return NS_OK; + } + + private: + ~PrintListenerAdapter() = default; + + RefPtr mPromise; +}; + +NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener) +#endif + +already_AddRefed CanonicalBrowsingContext::PrintJS( + nsIPrintSettings* aPrintSettings, ErrorResult& aRv) { + RefPtr promise = Promise::Create(GetIncumbentGlobal(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return promise.forget(); + } + + Print(aPrintSettings) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise](bool) { promise->MaybeResolveWithUndefined(); }, + [promise](nsresult aResult) { promise->MaybeReject(aResult); }); + return promise.forget(); +} + +RefPtr CanonicalBrowsingContext::Print( + nsIPrintSettings* aPrintSettings) { +#ifndef NS_PRINTING + return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); +#else + + auto promise = MakeRefPtr(__func__); + auto listener = MakeRefPtr(promise); + if (IsInProcess()) { + RefPtr outerWindow = + nsGlobalWindowOuter::Cast(GetDOMWindow()); + if (NS_WARN_IF(!outerWindow)) { + promise->Reject(NS_ERROR_FAILURE, __func__); + return promise; + } + + ErrorResult rv; + outerWindow->Print(aPrintSettings, + /* aRemotePrintJob = */ nullptr, listener, + /* aDocShellToCloneInto = */ nullptr, + nsGlobalWindowOuter::IsPreview::No, + nsGlobalWindowOuter::IsForWindowDotPrint::No, + /* aPrintPreviewCallback = */ nullptr, rv); + if (rv.Failed()) { + promise->Reject(rv.StealNSResult(), __func__); + } + return promise; + } + + auto* browserParent = GetBrowserParent(); + if (NS_WARN_IF(!browserParent)) { + promise->Reject(NS_ERROR_FAILURE, __func__); + return promise; + } + + nsCOMPtr printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (NS_WARN_IF(!printSettingsSvc)) { + promise->Reject(NS_ERROR_FAILURE, __func__); + return promise; + } + + nsresult rv; + nsCOMPtr printSettings = aPrintSettings; + if (!printSettings) { + rv = + printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(rv, __func__); + return promise; + } + } + + embedding::PrintData printData; + rv = printSettingsSvc->SerializeToPrintData(printSettings, &printData); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(rv, __func__); + return promise; + } + + layout::RemotePrintJobParent* remotePrintJob = + new layout::RemotePrintJobParent(printSettings); + printData.remotePrintJob() = + browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob); + + if (listener) { + remotePrintJob->RegisterListener(listener); + } + + if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) { + promise->Reject(NS_ERROR_FAILURE, __func__); + } + return promise.forget(); +#endif +} + +void CanonicalBrowsingContext::CallOnAllTopDescendants( + const std::function& + aCallback) { +#ifdef DEBUG + RefPtr parent = GetParentCrossChromeBoundary(); + MOZ_ASSERT(!parent, "Should only call on top chrome BC"); +#endif + + nsTArray> groups; + BrowsingContextGroup::GetAllGroups(groups); + for (auto& browsingContextGroup : groups) { + for (auto& bc : browsingContextGroup->Toplevels()) { + if (bc == this) { + // Cannot be a descendent of myself so skip. + continue; + } + + RefPtr top = + bc->Canonical()->TopCrossChromeBoundary(); + if (top == this) { + if (aCallback(bc->Canonical()) == CallState::Stop) { + return; + } + } + } + } +} + +void CanonicalBrowsingContext::SessionHistoryCommit( + uint64_t aLoadId, const nsID& aChangeID, uint32_t aLoadType, bool aPersist, + bool aCloneEntryChildren, bool aChannelExpired, uint32_t aCacheKey) { + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this, + aLoadId)); + MOZ_ASSERT(aLoadId != UINT64_MAX, + "Must not send special about:blank loadinfo to parent."); + for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { + if (mLoadingEntries[i].mLoadId == aLoadId) { + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (!shistory) { + SessionHistoryEntry::RemoveLoadId(aLoadId); + mLoadingEntries.RemoveElementAt(i); + return; + } + + RefPtr newActiveEntry = mLoadingEntries[i].mEntry; + if (aCacheKey != 0) { + newActiveEntry->SetCacheKey(aCacheKey); + } + + if (aChannelExpired) { + newActiveEntry->SharedInfo()->mExpired = true; + } + + bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad(); + newActiveEntry->SetForInitialLoad(false); + SessionHistoryEntry::RemoveLoadId(aLoadId); + mLoadingEntries.RemoveElementAt(i); + + int32_t indexOfHistoryLoad = -1; + if (loadFromSessionHistory) { + nsCOMPtr root = nsSHistory::GetRootSHEntry(newActiveEntry); + indexOfHistoryLoad = shistory->GetIndexOfEntry(root); + if (indexOfHistoryLoad < 0) { + // Entry has been removed from the session history. + return; + } + } + + CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); + + // If there is a name in the new entry, clear the name of all contiguous + // entries. This is for https://html.spec.whatwg.org/#history-traversal + // Step 4.4.2. + nsAutoString nameOfNewEntry; + newActiveEntry->GetName(nameOfNewEntry); + if (!nameOfNewEntry.IsEmpty()) { + nsSHistory::WalkContiguousEntries( + newActiveEntry, + [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); + } + + bool addEntry = ShouldUpdateSessionHistory(aLoadType); + if (IsTop()) { + if (mActiveEntry && !mActiveEntry->GetFrameLoader()) { + bool sharesDocument = true; + mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument); + if (!sharesDocument) { + // If the old page won't be in the bfcache, + // clear the dynamic entries. + RemoveDynEntriesFromActiveSessionHistoryEntry(); + } + } + + if (LOAD_TYPE_HAS_FLAGS(aLoadType, + nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { + // Replace the current entry with the new entry. + int32_t index = shistory->GetIndexForReplace(); + + // If we're trying to replace an inexistant shistory entry then we + // should append instead. + addEntry = index < 0; + if (!addEntry) { + shistory->ReplaceEntry(index, newActiveEntry); + } + mActiveEntry = newActiveEntry; + } else if (LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) && + !ShouldAddEntryForRefresh(newActiveEntry) && mActiveEntry) { + addEntry = false; + mActiveEntry->ReplaceWith(*newActiveEntry); + } else { + mActiveEntry = newActiveEntry; + } + + if (loadFromSessionHistory) { + // XXX Synchronize browsing context tree and session history tree? + shistory->InternalSetRequestedIndex(indexOfHistoryLoad); + shistory->UpdateIndex(); + + if (IsTop()) { + mActiveEntry->SetWireframe(Nothing()); + } + } else if (addEntry) { + shistory->AddEntry(mActiveEntry, aPersist); + shistory->InternalSetRequestedIndex(-1); + } + } else { + // FIXME The old implementations adds it to the parent's mLSHE if there + // is one, need to figure out if that makes sense here (peterv + // doesn't think it would). + if (loadFromSessionHistory) { + if (mActiveEntry) { + // mActiveEntry is null if we're loading iframes from session + // history while also parent page is loading from session history. + // In that case there isn't anything to sync. + mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(), + this); + } + mActiveEntry = newActiveEntry; + + shistory->InternalSetRequestedIndex(indexOfHistoryLoad); + // FIXME UpdateIndex() here may update index too early (but even the + // old implementation seems to have similar issues). + shistory->UpdateIndex(); + } else if (addEntry) { + if (mActiveEntry) { + if (LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) || + (LOAD_TYPE_HAS_FLAGS(aLoadType, + nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) && + !ShouldAddEntryForRefresh(newActiveEntry))) { + // FIXME We need to make sure that when we create the info we + // make a copy of the shared state. + mActiveEntry->ReplaceWith(*newActiveEntry); + } else { + // AddChildSHEntryHelper does update the index of the session + // history! + shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry, + Top(), aCloneEntryChildren); + mActiveEntry = newActiveEntry; + } + } else { + SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry; + // XXX What should happen if parent doesn't have mActiveEntry? + // Or can that even happen ever? + if (parentEntry) { + mActiveEntry = newActiveEntry; + // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite + // right, but aUseRemoteSubframes should be going away. + parentEntry->AddChild( + mActiveEntry, + CreatedDynamically() ? -1 : GetParent()->IndexOf(this), + IsInProcess()); + } + } + shistory->InternalSetRequestedIndex(-1); + } + } + + ResetSHEntryHasUserInteractionCache(); + + HistoryCommitIndexAndLength(aChangeID, caller); + + shistory->LogHistory(); + + return; + } + // XXX Should the loading entries before [i] be removed? + } + // FIXME Should we throw an error if we don't find an entry for + // aSessionHistoryEntryId? +} + +already_AddRefed CanonicalBrowsingContext::CreateLoadInfo( + SessionHistoryEntry* aEntry) { + const SessionHistoryInfo& info = aEntry->Info(); + RefPtr loadState(new nsDocShellLoadState(info.GetURI())); + info.FillLoadInfo(*loadState); + UniquePtr loadingInfo; + loadingInfo = MakeUnique(aEntry); + mLoadingEntries.AppendElement( + LoadingSessionHistoryEntry{loadingInfo->mLoadId, aEntry}); + loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo)); + + return loadState.forget(); +} + +void CanonicalBrowsingContext::NotifyOnHistoryReload( + bool aForceReload, bool& aCanReload, + Maybe>>& aLoadState, + Maybe& aReloadActiveEntry) { + MOZ_DIAGNOSTIC_ASSERT(!aLoadState); + + aCanReload = true; + nsISHistory* shistory = GetSessionHistory(); + NS_ENSURE_TRUE_VOID(shistory); + + shistory->NotifyOnHistoryReload(&aCanReload); + if (!aCanReload) { + return; + } + + if (mActiveEntry) { + aLoadState.emplace(WrapMovingNotNull(RefPtr{CreateLoadInfo(mActiveEntry)})); + aReloadActiveEntry.emplace(true); + if (aForceReload) { + shistory->RemoveFrameEntries(mActiveEntry); + } + } else if (!mLoadingEntries.IsEmpty()) { + const LoadingSessionHistoryEntry& loadingEntry = + mLoadingEntries.LastElement(); + uint64_t loadId = loadingEntry.mLoadId; + aLoadState.emplace( + WrapMovingNotNull(RefPtr{CreateLoadInfo(loadingEntry.mEntry)})); + aReloadActiveEntry.emplace(false); + if (aForceReload) { + SessionHistoryEntry::LoadingEntry* entry = + SessionHistoryEntry::GetByLoadId(loadId); + if (entry) { + shistory->RemoveFrameEntries(entry->mEntry); + } + } + } + + if (aLoadState) { + // Use 0 as the offset, since aLoadState will be be used for reload. + aLoadState.ref()->SetLoadIsFromSessionHistory(0, + aReloadActiveEntry.value()); + } + // If we don't have an active entry and we don't have a loading entry then + // the nsDocShell will create a load state based on its document. +} + +void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( + const Maybe& aPreviousScrollPos, SessionHistoryInfo* aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) { + nsISHistory* shistory = GetSessionHistory(); + if (!shistory) { + return; + } + CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); + + RefPtr oldActiveEntry = mActiveEntry; + if (aPreviousScrollPos.isSome() && oldActiveEntry) { + oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x, + aPreviousScrollPos.ref().y); + } + mActiveEntry = new SessionHistoryEntry(aInfo); + mActiveEntry->SetDocshellID(GetHistoryID()); + mActiveEntry->AdoptBFCacheEntry(oldActiveEntry); + if (aUpdatedCacheKey != 0) { + mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey; + } + + if (IsTop()) { + Maybe previousEntryIndex, loadedEntryIndex; + shistory->AddToRootSessionHistory( + true, oldActiveEntry, this, mActiveEntry, aLoadType, + nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr), + &previousEntryIndex, &loadedEntryIndex); + } else { + if (oldActiveEntry) { + shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(), + true); + } else if (GetParent() && GetParent()->mActiveEntry) { + GetParent()->mActiveEntry->AddChild( + mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this), + UseRemoteSubframes()); + } + } + + ResetSHEntryHasUserInteractionCache(); + + shistory->InternalSetRequestedIndex(-1); + + // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. + HistoryCommitIndexAndLength(aChangeID, caller); + + static_cast(shistory)->LogHistory(); +} + +void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry( + SessionHistoryInfo* aInfo) { + if (!mActiveEntry) { + return; + } + + mActiveEntry->SetInfo(aInfo); + // Notify children of the update + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (shistory) { + shistory->NotifyOnHistoryReplaceEntry(); + shistory->UpdateRootBrowsingContextState(); + } + + ResetSHEntryHasUserInteractionCache(); + + if (IsTop()) { + mActiveEntry->SetWireframe(Nothing()); + } + + // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. +} + +void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { + nsISHistory* shistory = GetSessionHistory(); + // In theory shistory can be null here if the method is called right after + // CanonicalBrowsingContext::ReplacedBy call. + NS_ENSURE_TRUE_VOID(shistory); + nsCOMPtr root = nsSHistory::GetRootSHEntry(mActiveEntry); + shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry); +} + +void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) { + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (shistory) { + CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); + nsCOMPtr root = nsSHistory::GetRootSHEntry(mActiveEntry); + bool didRemove; + AutoTArray ids({GetHistoryID()}); + shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove); + if (didRemove) { + RefPtr rootBC = shistory->GetBrowsingContext(); + if (rootBC) { + if (!rootBC->IsInProcess()) { + if (ContentParent* cp = rootBC->Canonical()->GetContentParent()) { + Unused << cp->SendDispatchLocationChangeEvent(rootBC); + } + } else if (rootBC->GetDocShell()) { + rootBC->GetDocShell()->DispatchLocationChangeEvent(); + } + } + } + HistoryCommitIndexAndLength(aChangeID, caller); + } +} + +Maybe CanonicalBrowsingContext::HistoryGo( + int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction, + bool aUserActivation, Maybe aContentId) { + if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) { + NS_ERROR( + "aRequireUserInteraction may only be used with an offset of -1 or 1"); + return Nothing(); + } + + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (!shistory) { + return Nothing(); + } + + CheckedInt index = shistory->GetRequestedIndex() >= 0 + ? shistory->GetRequestedIndex() + : shistory->Index(); + MOZ_LOG(gSHLog, LogLevel::Debug, + ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset, + (index + aOffset).value(), aHistoryEpoch, + (uint64_t)(aContentId.isSome() ? aContentId.value() : 0))); + + while (true) { + index += aOffset; + if (!index.isValid()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index")); + return Nothing(); + } + + // Check for user interaction if desired, except for the first and last + // history entries. We compare with >= to account for the case where + // aOffset >= length. + if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 || + index.value() <= 0) { + break; + } + if (shistory->HasUserInteractionAtIndex(index.value())) { + break; + } + } + + // Implement aborting additional history navigations from within the same + // event spin of the content process. + + uint64_t epoch; + bool sameEpoch = false; + Maybe id; + shistory->GetEpoch(epoch, id); + + if (aContentId == id && epoch >= aHistoryEpoch) { + sameEpoch = true; + MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id")); + } + // Don't update the epoch until we know if the target index is valid + + // GoToIndex checks that index is >= 0 and < length. + nsTArray loadResults; + nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch, + aOffset == 0, aUserActivation); + if (NS_FAILED(rv)) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Dropping HistoryGo - bad index or same epoch (not in same doc)")); + return Nothing(); + } + if (epoch < aHistoryEpoch || aContentId != id) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch")); + shistory->SetEpoch(aHistoryEpoch, aContentId); + } + int32_t requestedIndex = shistory->GetRequestedIndex(); + nsSHistory::LoadURIs(loadResults); + return Some(requestedIndex); +} + +JSObject* CanonicalBrowsingContext::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto); +} + +void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) { + Element* element = Top()->GetEmbedderElement(); + if (!element) { + return; + } + + auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns; + auto dispatcher = MakeRefPtr( + element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes); + dispatcher->PostDOMEvent(); +} + +void CanonicalBrowsingContext::CanonicalDiscard() { + if (mTabMediaController) { + mTabMediaController->Shutdown(); + mTabMediaController = nullptr; + } + + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED, + "CanonicalBrowsingContext::CanonicalDiscard"_ns); + } + + if (mWebProgress) { + RefPtr progress = mWebProgress; + progress->ContextDiscarded(); + } + + if (IsTop()) { + BackgroundSessionStorageManager::RemoveManager(Id()); + } + + CancelSessionStoreUpdate(); + + if (UsePrivateBrowsing() && EverAttached() && IsContent()) { + DecreasePrivateCount(); + } +} + +void CanonicalBrowsingContext::CanonicalAttach() { + if (UsePrivateBrowsing() && IsContent()) { + IncreasePrivateCount(); + } +} + +void CanonicalBrowsingContext::AddPendingDiscard() { + MOZ_ASSERT(!mFullyDiscarded); + mPendingDiscards++; +} + +void CanonicalBrowsingContext::RemovePendingDiscard() { + mPendingDiscards--; + if (!mPendingDiscards) { + mFullyDiscarded = true; + auto listeners = std::move(mFullyDiscardedListeners); + for (const auto& listener : listeners) { + listener(Id()); + } + } +} + +void CanonicalBrowsingContext::AddFinalDiscardListener( + std::function&& aListener) { + if (mFullyDiscarded) { + aListener(Id()); + return; + } + mFullyDiscardedListeners.AppendElement(std::move(aListener)); +} + +void CanonicalBrowsingContext::SetForceAppWindowActive(bool aForceActive, + ErrorResult& aRv) { + MOZ_DIAGNOSTIC_ASSERT(IsChrome()); + MOZ_DIAGNOSTIC_ASSERT(IsTop()); + if (!IsChrome() || !IsTop()) { + return aRv.ThrowNotAllowedError( + "You shouldn't need to force this BrowsingContext to be active, use " + ".isActive instead"); + } + if (mForceAppWindowActive == aForceActive) { + return; + } + mForceAppWindowActive = aForceActive; + RecomputeAppWindowVisibility(); +} + +void CanonicalBrowsingContext::RecomputeAppWindowVisibility() { + MOZ_RELEASE_ASSERT(IsChrome()); + MOZ_RELEASE_ASSERT(IsTop()); + + const bool isActive = [&] { + if (ForceAppWindowActive()) { + return true; + } + auto* docShell = GetDocShell(); + if (NS_WARN_IF(!docShell)) { + return false; + } + nsCOMPtr widget; + nsDocShell::Cast(docShell)->GetMainWidget(getter_AddRefs(widget)); + if (NS_WARN_IF(!widget)) { + return false; + } + if (widget->IsFullyOccluded() || + widget->SizeMode() == nsSizeMode_Minimized) { + return false; + } + return true; + }(); + SetIsActive(isActive, IgnoreErrors()); +} + +void CanonicalBrowsingContext::AdjustPrivateBrowsingCount( + bool aPrivateBrowsing) { + if (IsDiscarded() || !EverAttached() || IsChrome()) { + return; + } + + MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing()); + if (aPrivateBrowsing) { + IncreasePrivateCount(); + } else { + DecreasePrivateCount(); + } +} + +void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() { + WindowContext* windowContext = GetCurrentWindowContext(); + if (!windowContext) { + return; + } + + // As this function would only be called when user click the play icon on the + // tab bar. That's clear user intent to play, so gesture activate the window + // context so that the block-autoplay logic allows the media to autoplay. + windowContext->NotifyUserGestureActivation(); + AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64, + Id()); + StartDelayedAutoplayMediaComponents(); + // Notfiy all content browsing contexts which are related with the canonical + // browsing content tree to start delayed autoplay media. + + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendStartDelayedAutoplayMediaComponents(this); + }); +} + +void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted, + ErrorResult& aRv) { + MOZ_ASSERT(!GetParent(), + "Notify media mute change on non top-level context!"); + SetMuted(aMuted, aRv); +} + +uint32_t CanonicalBrowsingContext::CountSiteOrigins( + GlobalObject& aGlobal, + const Sequence>& aRoots) { + nsTHashSet uniqueSiteOrigins; + + for (const auto& root : aRoots) { + root->PreOrderWalk([&](BrowsingContext* aContext) { + WindowGlobalParent* windowGlobalParent = + aContext->Canonical()->GetCurrentWindowGlobal(); + if (windowGlobalParent) { + nsIPrincipal* documentPrincipal = + windowGlobalParent->DocumentPrincipal(); + + bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal(); + if (isContentPrincipal) { + nsCString siteOrigin; + documentPrincipal->GetSiteOrigin(siteOrigin); + uniqueSiteOrigins.Insert(siteOrigin); + } + } + }); + } + + return uniqueSiteOrigins.Count(); +} + +/* static */ +bool CanonicalBrowsingContext::IsPrivateBrowsingActive() { + return gNumberOfPrivateContexts > 0; +} + +void CanonicalBrowsingContext::UpdateMediaControlAction( + const MediaControlAction& aAction) { + if (IsDiscarded()) { + return; + } + ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction); + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendUpdateMediaControlAction(this, aAction); + }); +} + +void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, + const LoadURIOptions& aOptions, + ErrorResult& aError) { + RefPtr loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + this, aURI, aOptions, getter_AddRefs(loadState)); + MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI); + + if (NS_FAILED(rv)) { + aError.Throw(rv); + return; + } + + LoadURI(loadState, true); +} + +void CanonicalBrowsingContext::FixupAndLoadURIString( + const nsAString& aURI, const LoadURIOptions& aOptions, + ErrorResult& aError) { + RefPtr loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + this, aURI, aOptions, getter_AddRefs(loadState)); + + if (rv == NS_ERROR_MALFORMED_URI) { + DisplayLoadError(aURI); + return; + } + + if (NS_FAILED(rv)) { + aError.Throw(rv); + return; + } + + LoadURI(loadState, true); +} + +void CanonicalBrowsingContext::GoBack( + const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction, bool aUserActivation) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns); + } + + if (RefPtr docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GoBack(aRequireUserInteraction, aUserActivation); + } else if (ContentParent* cp = GetContentParent()) { + Maybe cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoBack(this, cancelContentJSEpoch, + aRequireUserInteraction, aUserActivation); + } +} +void CanonicalBrowsingContext::GoForward( + const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction, bool aUserActivation) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns); + } + + if (RefPtr docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GoForward(aRequireUserInteraction, aUserActivation); + } else if (ContentParent* cp = GetContentParent()) { + Maybe cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoForward(this, cancelContentJSEpoch, + aRequireUserInteraction, aUserActivation); + } +} +void CanonicalBrowsingContext::GoToIndex( + int32_t aIndex, const Optional& aCancelContentJSEpoch, + bool aUserActivation) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns); + } + + if (RefPtr docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GotoIndex(aIndex, aUserActivation); + } else if (ContentParent* cp = GetContentParent()) { + Maybe cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch, + aUserActivation); + } +} +void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns); + } + + if (RefPtr docShell = nsDocShell::Cast(GetDocShell())) { + docShell->Reload(aReloadFlags); + } else if (ContentParent* cp = GetContentParent()) { + Unused << cp->SendReload(this, aReloadFlags); + } +} + +void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED, + "CanonicalBrowsingContext::Stop"_ns); + } + + // Ask the docshell to stop to handle loads that haven't + // yet reached here, as well as non-network activity. + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + docShell->Stop(aStopFlags); + } else if (ContentParent* cp = GetContentParent()) { + Unused << cp->SendStopLoad(this, aStopFlags); + } +} + +void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() { + if (!mPromise) { + return; + } + + if (mContentParent) { + // If our new content process is still unloading from a previous process + // switch, wait for that unload to complete before continuing. + auto found = mTarget->FindUnloadingHost(mContentParent->ChildID()); + if (found != mTarget->mUnloadingHosts.end()) { + found->mCallbacks.AppendElement( + [self = RefPtr{this}]() { self->ProcessReady(); }); + return; + } + } + + ProcessReady(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() { + if (!mPromise) { + return; + } + + MOZ_ASSERT(!mProcessReady); + mProcessReady = true; + MaybeFinish(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::MaybeFinish() { + if (!mPromise) { + return; + } + + if (!mProcessReady || mWaitingForPrepareToChange) { + return; + } + + // If this BrowsingContext is embedded within the parent process, perform the + // process switch directly. + nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe(); + if (NS_FAILED(rv)) { + NS_WARNING("Error finishing PendingRemotenessChange!"); + Cancel(rv); + } else { + Clear(); + } +} + +// Logic for finishing a toplevel process change embedded within the parent +// process. Due to frontend integration the logic differs substantially from +// subframe process switches, and is handled separately. +nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() { + MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(), + "We shouldn't be trying to change the remoteness of " + "non-remote iframes"); + + // Abort if our ContentParent died while process switching. + if (mContentParent && NS_WARN_IF(mContentParent->IsShuttingDown())) { + return NS_ERROR_FAILURE; + } + + // While process switching, we need to check if any of our ancestors are + // discarded or no longer current, in which case the process switch needs to + // be aborted. + RefPtr target(mTarget); + if (target->IsDiscarded() || !target->AncestorsAreCurrent()) { + return NS_ERROR_FAILURE; + } + + Element* browserElement = target->GetEmbedderElement(); + if (!browserElement) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr browser = browserElement->AsBrowser(); + if (!browser) { + return NS_ERROR_FAILURE; + } + + RefPtr frameLoaderOwner = do_QueryObject(browserElement); + MOZ_RELEASE_ASSERT(frameLoaderOwner, + "embedder browser must be nsFrameLoaderOwner"); + + // If we're process switching a browsing context in private browsing + // mode we might decrease the private browsing count to '0', which + // would make us fire "last-pb-context-exited" and drop the private + // session. To prevent that we artificially increment the number of + // private browsing contexts with '1' until the process switch is done. + bool usePrivateBrowsing = mTarget->UsePrivateBrowsing(); + if (usePrivateBrowsing) { + IncreasePrivateCount(); + } + + auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() { + if (usePrivateBrowsing) { + DecreasePrivateCount(); + } + }); + + // Tell frontend code that this browser element is about to change process. + nsresult rv = browser->BeforeChangeRemoteness(); + if (NS_FAILED(rv)) { + return rv; + } + + // Some frontend code checks the value of the `remote` attribute on the + // browser to determine if it is remote, so update the value. + browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote, + mContentParent ? u"true"_ns : u"false"_ns, + /* notify */ true); + + // The process has been created, hand off to nsFrameLoaderOwner to finish + // the process switch. + ErrorResult error; + frameLoaderOwner->ChangeRemotenessToProcess(mContentParent, mOptions, + mSpecificGroup, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + // Tell frontend the load is done. + bool loadResumed = false; + rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We did it! The process switch is complete. + RefPtr frameLoader = frameLoaderOwner->GetFrameLoader(); + RefPtr newBrowser = frameLoader->GetBrowserParent(); + if (!newBrowser) { + if (mContentParent) { + // Failed to create the BrowserParent somehow! Abort the process switch + // attempt. + return NS_ERROR_UNEXPECTED; + } + + if (!loadResumed) { + RefPtr newDocShell = frameLoader->GetDocShell(error); + if (error.Failed()) { + return error.StealNSResult(); + } + + rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId, + /* aHistoryIndex */ -1); + if (NS_FAILED(rv)) { + return rv; + } + } + } else if (!loadResumed) { + newBrowser->ResumeLoad(mPendingSwitchId); + } + + mPromise->Resolve(newBrowser, __func__); + return NS_OK; +} + +nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() { + MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext, + "Cannot replace BC for subframe"); + MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop()); + + // While process switching, we need to check if any of our ancestors are + // discarded or no longer current, in which case the process switch needs to + // be aborted. + RefPtr target(mTarget); + if (target->IsDiscarded() || !target->AncestorsAreCurrent()) { + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!mContentParent)) { + return NS_ERROR_FAILURE; + } + + RefPtr embedderWindow = target->GetParentWindowContext(); + if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) { + return NS_ERROR_FAILURE; + } + + RefPtr embedderBrowser = embedderWindow->GetBrowserParent(); + if (NS_WARN_IF(!embedderBrowser)) { + return NS_ERROR_FAILURE; + } + + // If we're creating a new remote browser, and the host process is already + // dead, abort the process switch. + if (mContentParent != embedderBrowser->Manager() && + NS_WARN_IF(mContentParent->IsShuttingDown())) { + return NS_ERROR_FAILURE; + } + + RefPtr oldBrowser = target->GetBrowserParent(); + target->SetCurrentBrowserParent(nullptr); + + // If we were in a remote frame, trigger unloading of the remote window. The + // previous BrowserParent is registered in `mUnloadingHosts` and will only be + // cleared when the BrowserParent is fully destroyed. + bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target; + if (wasRemote) { + MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser); + MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() || + oldBrowser->GetBrowserBridgeParent()); + + // `oldBrowser` will clear the `UnloadingHost` status once the actor has + // been destroyed. + if (oldBrowser->CanSend()) { + target->StartUnloadingHost(oldBrowser->Manager()->ChildID()); + Unused << oldBrowser->SendWillChangeProcess(); + oldBrowser->Destroy(); + } + } + + // Update which process is considered the current owner + target->SetOwnerProcessId(mContentParent->ChildID()); + + // If we're switching from remote to local, we don't need to create a + // BrowserBridge, and can instead perform the switch directly. + if (mContentParent == embedderBrowser->Manager()) { + MOZ_DIAGNOSTIC_ASSERT( + mPendingSwitchId, + "We always have a PendingSwitchId, except for print-preview loads, " + "which will never perform a process-switch to being in-process with " + "their embedder"); + MOZ_DIAGNOSTIC_ASSERT(wasRemote, + "Attempt to process-switch from local to local?"); + + target->SetCurrentBrowserParent(embedderBrowser); + Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId); + mPromise->Resolve(embedderBrowser, __func__); + return NS_OK; + } + + // The BrowsingContext will be remote, either as an already-remote frame + // changing processes, or as a local frame becoming remote. Construct a new + // BrowserBridgeParent to host the remote content. + target->SetCurrentBrowserParent(nullptr); + + MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(), + "Not supported without fission"); + uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + if (target->UsePrivateBrowsing()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + + nsCOMPtr initialPrincipal = + NullPrincipal::Create(target->OriginAttributesRef()); + WindowGlobalInit windowInit = + WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal); + + // Create and initialize our new BrowserBridgeParent. + TabId tabId(nsContentUtils::GenerateTabId()); + RefPtr bridge = new BrowserBridgeParent(); + nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent, + windowInit, chromeFlags, tabId); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If we've already destroyed our previous document, make a best-effort + // attempt to recover from this failure and show the crashed tab UI. We only + // do this in the previously-remote case, as previously in-process frames + // will have their navigation cancelled, and will remain visible. + if (wasRemote) { + target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent()); + } + return rv; + } + + // Tell the embedder process a remoteness change is in-process. When this is + // acknowledged, reset the in-flight ID if it used to be an in-process load. + RefPtr newBrowser = bridge->GetBrowserParent(); + { + // If we weren't remote, mark our embedder window browser as unloading until + // our embedder process has acked our MakeFrameRemote message. + Maybe clearChildID; + if (!wasRemote) { + clearChildID = Some(embedderBrowser->Manager()->ChildID()); + target->StartUnloadingHost(*clearChildID); + } + auto callback = [target, clearChildID](auto&&) { + if (clearChildID) { + target->ClearUnloadingHost(*clearChildID); + } + }; + + ManagedEndpoint endpoint = + embedderBrowser->OpenPBrowserBridgeEndpoint(bridge); + MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid()); + embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId, + newBrowser->GetLayersId(), callback, + callback); + } + + // Resume the pending load in our new process. + if (mPendingSwitchId) { + newBrowser->ResumeLoad(mPendingSwitchId); + } + + // We did it! The process switch is complete. + mPromise->Resolve(newBrowser, __func__); + return NS_OK; +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) { + if (!mPromise) { + return; + } + + mPromise->Reject(aRv, __func__); + Clear(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Clear() { + // Make sure we don't die while we're doing cleanup. + RefPtr kungFuDeathGrip(this); + if (mTarget) { + MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this); + mTarget->mPendingRemotenessChange = nullptr; + } + + // When this PendingRemotenessChange was created, it was given a + // `mContentParent`. + if (mContentParent) { + mContentParent->RemoveKeepAlive(); + mContentParent = nullptr; + } + + // If we were given a specific group, stop keeping that group alive manually. + if (mSpecificGroup) { + mSpecificGroup->RemoveKeepAlive(); + mSpecificGroup = nullptr; + } + + mPromise = nullptr; + mTarget = nullptr; +} + +CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange( + CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise, + uint64_t aPendingSwitchId, const NavigationIsolationOptions& aOptions) + : mTarget(aTarget), + mPromise(aPromise), + mPendingSwitchId(aPendingSwitchId), + mOptions(aOptions) {} + +CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() { + MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup, + "should've already been Cancel() or Complete()-ed"); +} + +BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const { + return mCurrentBrowserParent; +} + +void CanonicalBrowsingContext::SetCurrentBrowserParent( + BrowserParent* aBrowserParent) { + MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent, + "BrowsingContext already has a current BrowserParent!"); + MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend()); + MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, + aBrowserParent->Manager()->ChildID() == mProcessId); + + // BrowserParent must either be directly for this BrowsingContext, or the + // manager out our embedder WindowGlobal. + MOZ_DIAGNOSTIC_ASSERT_IF( + aBrowserParent && aBrowserParent->GetBrowsingContext() != this, + GetParentWindowContext() && + GetParentWindowContext()->Manager() == aBrowserParent); + + mCurrentBrowserParent = aBrowserParent; +} + +RefPtr +CanonicalBrowsingContext::ChangeRemoteness( + const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId) { + MOZ_DIAGNOSTIC_ASSERT(IsContent(), + "cannot change the process of chrome contexts"); + MOZ_DIAGNOSTIC_ASSERT( + IsTop() == IsEmbeddedInProcess(0), + "toplevel content must be embedded in the parent process"); + MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext || IsTop(), + "Cannot replace BrowsingContext for subframes"); + MOZ_DIAGNOSTIC_ASSERT( + aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext, + "Cannot specify group ID unless replacing BC"); + MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(), + "Should always have aPendingSwitchId for top-level " + "frames"); + + if (!AncestorsAreCurrent()) { + NS_WARNING("An ancestor context is no longer current"); + return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // Ensure our embedder hasn't been destroyed or asked to shutdown already. + RefPtr embedderWindowGlobal = GetEmbedderWindowGlobal(); + if (!embedderWindowGlobal) { + NS_WARNING("Non-embedded BrowsingContext"); + return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__); + } + + if (!embedderWindowGlobal->CanSend()) { + NS_WARNING("Embedder already been destroyed."); + return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); + } + + RefPtr embedderBrowser = + embedderWindowGlobal->GetBrowserParent(); + if (embedderBrowser && embedderBrowser->Manager()->IsShuttingDown()) { + NS_WARNING("Embedder already asked to shutdown."); + return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); + } + + if (aOptions.mRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) { + NS_WARNING("Cannot load non-remote subframes"); + return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // Cancel ongoing remoteness changes. + if (mPendingRemotenessChange) { + mPendingRemotenessChange->Cancel(NS_ERROR_ABORT); + MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared"); + } + + auto promise = MakeRefPtr(__func__); + promise->UseDirectTaskDispatch(__func__); + + RefPtr change = + new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions); + mPendingRemotenessChange = change; + + // If a specific BrowsingContextGroup ID was specified for this load, make + // sure to keep it alive until the process switch is completed. + if (aOptions.mSpecificGroupId) { + change->mSpecificGroup = + BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId); + change->mSpecificGroup->AddKeepAlive(); + } + + // Call `prepareToChangeRemoteness` in parallel with starting a new process + // for loads. + if (IsTop() && GetEmbedderElement()) { + nsCOMPtr browser = GetEmbedderElement()->AsBrowser(); + if (!browser) { + change->Cancel(NS_ERROR_FAILURE); + return promise.forget(); + } + + RefPtr blocker; + nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker)); + if (NS_FAILED(rv)) { + change->Cancel(rv); + return promise.forget(); + } + + // Mark prepareToChange as unresolved, and wait for it to become resolved. + if (blocker && blocker->State() != Promise::PromiseState::Resolved) { + change->mWaitingForPrepareToChange = true; + RefPtr listener = new DomPromiseListener( + [change](JSContext* aCx, JS::Handle aValue) { + change->mWaitingForPrepareToChange = false; + change->MaybeFinish(); + }, + [change](nsresult aRv) { change->Cancel(aRv); }); + blocker->AppendNativeHandler(listener); + } + } + + // Switching a subframe to be local within it's embedding process. + if (embedderBrowser && + aOptions.mRemoteType == embedderBrowser->Manager()->GetRemoteType()) { + MOZ_DIAGNOSTIC_ASSERT( + aPendingSwitchId, + "We always have a PendingSwitchId, except for print-preview loads, " + "which will never perform a process-switch to being in-process with " + "their embedder"); + MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext); + MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty()); + MOZ_DIAGNOSTIC_ASSERT(!change->mWaitingForPrepareToChange); + MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup); + + // Switching to local, so we don't need to create a new process, and will + // instead use our embedder process. + change->mContentParent = embedderBrowser->Manager(); + change->mContentParent->AddKeepAlive(); + change->ProcessLaunched(); + return promise.forget(); + } + + // Switching to the parent process. + if (aOptions.mRemoteType.IsEmpty()) { + change->ProcessLaunched(); + return promise.forget(); + } + + // If we're aiming to end up in a new process of the same type as our old + // process, and then putting our previous document in the BFCache, try to stay + // in the same process to avoid creating new processes unnecessarily. + RefPtr existingProcess = GetContentParent(); + if (existingProcess && !existingProcess->IsShuttingDown() && + aOptions.mReplaceBrowsingContext && + aOptions.mRemoteType == existingProcess->GetRemoteType()) { + change->mContentParent = existingProcess; + change->mContentParent->AddKeepAlive(); + change->ProcessLaunched(); + return promise.forget(); + } + + // Try to predict which BrowsingContextGroup will be used for the final load + // in this BrowsingContext. This has to be accurate if switching into an + // existing group, as it will control what pool of processes will be used + // for process selection. + // + // It's _technically_ OK to provide a group here if we're actually going to + // switch into a brand new group, though it's sub-optimal, as it can + // restrict the set of processes we're using. + BrowsingContextGroup* finalGroup = + aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group(); + + bool preferUsed = + StaticPrefs::browser_tabs_remote_subframesPreferUsed() && !IsTop(); + + change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( + /* aRemoteType = */ aOptions.mRemoteType, + /* aGroup = */ finalGroup, + /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND, + /* aPreferUsed = */ preferUsed); + if (!change->mContentParent) { + change->Cancel(NS_ERROR_FAILURE); + return promise.forget(); + } + + // Add a KeepAlive used by this ContentParent, which will be cleared when + // the change is complete. This should prevent the process dying before + // we're ready to use it. + change->mContentParent->AddKeepAlive(); + if (change->mContentParent->IsLaunching()) { + change->mContentParent->WaitForLaunchAsync()->Then( + GetMainThreadSerialEventTarget(), __func__, + [change](ContentParent*) { change->ProcessLaunched(); }, + [change]() { change->Cancel(NS_ERROR_FAILURE); }); + } else { + change->ProcessLaunched(); + } + return promise.forget(); +} + +void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) { + MOZ_DIAGNOSTIC_ASSERT(IsTop()); + + if (aEmbedder) { + if (nsCOMPtr browser = aEmbedder->AsBrowser()) { + JS::Rooted key(RootingCx()); + if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) { + mPermanentKey = key; + } + } + } +} + +MediaController* CanonicalBrowsingContext::GetMediaController() { + // We would only create one media controller per tab, so accessing the + // controller via the top-level browsing context. + if (GetParent()) { + return Cast(Top())->GetMediaController(); + } + + MOZ_ASSERT(!GetParent(), + "Must access the controller from the top-level browsing context!"); + // Only content browsing context can create media controller, we won't create + // controller for chrome document, such as the browser UI. + if (!mTabMediaController && !IsDiscarded() && IsContent()) { + mTabMediaController = new MediaController(Id()); + } + return mTabMediaController; +} + +bool CanonicalBrowsingContext::HasCreatedMediaController() const { + return !!mTabMediaController; +} + +bool CanonicalBrowsingContext::SupportsLoadingInParent( + nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) { + // We currently don't support initiating loads in the parent when they are + // watched by devtools. This is because devtools tracks loads using content + // process notifications, which happens after the load is initiated in this + // case. Devtools clears all prior requests when it detects a new navigation, + // so it drops the main document load that happened here. + if (WatchedByDevTools()) { + return false; + } + + // Session-history-in-parent implementation relies currently on getting a + // round trip through a child process. + if (aLoadState->LoadIsFromSessionHistory()) { + return false; + } + + // DocumentChannel currently only supports connecting channels into the + // content process, so we can only support schemes that will always be loaded + // there for now. Restrict to just http(s) for simplicity. + if (!net::SchemeIsHTTP(aLoadState->URI()) && + !net::SchemeIsHTTPS(aLoadState->URI())) { + return false; + } + + if (WindowGlobalParent* global = GetCurrentWindowGlobal()) { + nsCOMPtr currentURI = global->GetDocumentURI(); + if (currentURI) { + bool newURIHasRef = false; + aLoadState->URI()->GetHasRef(&newURIHasRef); + bool equalsExceptRef = false; + aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef); + + if (equalsExceptRef && newURIHasRef) { + // This navigation is same-doc WRT the current one, we should pass it + // down to the docshell to be handled. + return false; + } + } + // If the current document has a beforeunload listener, then we need to + // start the load in that process after we fire the event. + if (global->HasBeforeUnload()) { + return false; + } + + *aOuterWindowId = global->OuterWindowId(); + } + return true; +} + +bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState, + bool aSetNavigating) { + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + if (!IsTopContent() || !GetContentParent() || + !StaticPrefs::browser_tabs_documentchannel_parent_controlled()) { + return false; + } + + uint64_t outerWindowId = 0; + if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { + return false; + } + + MOZ_ASSERT(!net::SchemeIsJavascript(aLoadState->URI())); + + MOZ_ALWAYS_SUCCEEDS( + SetParentInitiatedNavigationEpoch(++gParentInitiatedNavigationEpoch)); + // Note: If successful, this will recurse into StartDocumentLoad and + // set mCurrentLoad to the DocumentLoadListener instance created. + // Ideally in the future we will only start loads from here, and we can + // just set this directly instead. + return net::DocumentLoadListener::LoadInParent(this, aLoadState, + aSetNavigating); +} + +bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent( + nsDocShellLoadState* aLoadState) { + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + if (!IsTopContent() || !GetContentParent() || + (StaticPrefs::browser_tabs_documentchannel_parent_controlled())) { + return false; + } + + uint64_t outerWindowId = 0; + if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { + return false; + } + + // If we successfully open the DocumentChannel, then it'll register + // itself using aLoadIdentifier and be kept alive until it completes + // loading. + return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState); +} + +bool CanonicalBrowsingContext::StartDocumentLoad( + net::DocumentLoadListener* aLoad) { + // If we're controlling loads from the parent, then starting a new load means + // that we need to cancel any existing ones. + if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() && + mCurrentLoad) { + // Make sure we are not loading a javascript URI. + MOZ_ASSERT(!aLoad->IsLoadingJSURI()); + + // If we want to do a download, don't cancel the current navigation. + if (!aLoad->IsDownload()) { + mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns); + } + } + mCurrentLoad = aLoad; + + if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) { + mCurrentLoad = nullptr; + return false; + } + + return true; +} + +void CanonicalBrowsingContext::EndDocumentLoad(bool aContinueNavigating) { + mCurrentLoad = nullptr; + + if (!aContinueNavigating) { + // Resetting the current load identifier on a discarded context + // has no effect when a document load has finished. + Unused << SetCurrentLoadIdentifier(Nothing()); + } +} + +already_AddRefed CanonicalBrowsingContext::GetCurrentURI() const { + nsCOMPtr currentURI; + if (nsIDocShell* docShell = GetDocShell()) { + MOZ_ALWAYS_SUCCEEDS( + nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI))); + } else { + currentURI = mCurrentRemoteURI; + } + return currentURI.forget(); +} + +void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) { + MOZ_ASSERT(!GetDocShell()); + mCurrentRemoteURI = aCurrentRemoteURI; +} + +void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() { + WindowContext* topWc = GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false)); + } +} + +void CanonicalBrowsingContext::HistoryCommitIndexAndLength() { + nsID changeID = {}; + CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr); + HistoryCommitIndexAndLength(changeID, caller); +} +void CanonicalBrowsingContext::HistoryCommitIndexAndLength( + const nsID& aChangeID, + const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) { + if (!IsTop()) { + Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller); + return; + } + + nsISHistory* shistory = GetSessionHistory(); + if (!shistory) { + return; + } + int32_t index = 0; + shistory->GetIndex(&index); + int32_t length = shistory->GetCount(); + + GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID); + + shistory->EvictOutOfRangeContentViewers(index); + + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length, + aChangeID); + }); +} + +void CanonicalBrowsingContext::SynchronizeLayoutHistoryState() { + if (mActiveEntry) { + if (IsInProcess()) { + nsIDocShell* docShell = GetDocShell(); + if (docShell) { + docShell->PersistLayoutHistoryState(); + + nsCOMPtr state; + docShell->GetLayoutHistoryState(getter_AddRefs(state)); + if (state) { + mActiveEntry->SetLayoutHistoryState(state); + } + } + } else if (ContentParent* cp = GetContentParent()) { + cp->SendGetLayoutHistoryState(this)->Then( + GetCurrentSerialEventTarget(), __func__, + [activeEntry = mActiveEntry]( + const std::tuple, Maybe>& + aResult) { + if (std::get<0>(aResult)) { + activeEntry->SetLayoutHistoryState(std::get<0>(aResult)); + } + if (std::get<1>(aResult)) { + activeEntry->SetWireframe(std::get<1>(aResult)); + } + }, + []() {}); + } + } +} + +void CanonicalBrowsingContext::ResetScalingZoom() { + // This currently only ever gets called in the parent process, and we + // pass the message on to the WindowGlobalChild for the rootmost browsing + // context. + if (WindowGlobalParent* topWindow = GetTopWindowContext()) { + Unused << topWindow->SendResetScalingZoom(); + } +} + +void CanonicalBrowsingContext::SetRestoreData(SessionStoreRestoreData* aData, + ErrorResult& aError) { + MOZ_DIAGNOSTIC_ASSERT(aData); + + nsCOMPtr global = do_QueryInterface(GetParentObject()); + RefPtr promise = Promise::Create(global, aError); + if (aError.Failed()) { + return; + } + + if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) { + aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + mRestoreState = new RestoreState(); + mRestoreState->mData = aData; + mRestoreState->mPromise = promise; +} + +already_AddRefed CanonicalBrowsingContext::GetRestorePromise() { + if (mRestoreState) { + return do_AddRef(mRestoreState->mPromise); + } + return nullptr; +} + +void CanonicalBrowsingContext::ClearRestoreState() { + if (!mRestoreState) { + MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData()); + return; + } + if (mRestoreState->mPromise) { + mRestoreState->mPromise->MaybeRejectWithUndefined(); + } + mRestoreState = nullptr; + MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false)); +} + +void CanonicalBrowsingContext::RequestRestoreTabContent( + WindowGlobalParent* aWindow) { + MOZ_DIAGNOSTIC_ASSERT(IsTop()); + + if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) { + return; + } + + CanonicalBrowsingContext* context = aWindow->GetBrowsingContext(); + MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded()); + + RefPtr data = + mRestoreState->mData->FindDataForChild(context); + + if (context->IsTop()) { + MOZ_DIAGNOSTIC_ASSERT(context == this); + + // We need to wait until the appropriate load event has fired before we + // can "complete" the restore process, so if we're holding an empty data + // object, just resolve the promise immediately. + if (mRestoreState->mData->IsEmpty()) { + MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty()); + mRestoreState->Resolve(); + ClearRestoreState(); + return; + } + + // Since we're following load event order, we'll only arrive here for a + // toplevel context after we've already sent down data for all child frames, + // so it's safe to clear this reference now. The completion callback below + // relies on the mData field being null to determine if all requests have + // been sent out. + mRestoreState->ClearData(); + MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false)); + } + + if (data && !data->IsEmpty()) { + auto onTabRestoreComplete = [self = RefPtr{this}, + state = RefPtr{mRestoreState}](auto) { + state->mResolves++; + if (!state->mData && state->mRequests == state->mResolves) { + state->Resolve(); + if (state == self->mRestoreState) { + self->ClearRestoreState(); + } + } + }; + + mRestoreState->mRequests++; + + if (data->CanRestoreInto(aWindow->GetDocumentURI())) { + if (!aWindow->IsInProcess()) { + aWindow->SendRestoreTabContent(WrapNotNull(data.get()), + onTabRestoreComplete, + onTabRestoreComplete); + return; + } + data->RestoreInto(context); + } + + // This must be called both when we're doing an in-process restore, and when + // we didn't do a restore at all due to a URL mismatch. + onTabRestoreComplete(true); + } +} + +void CanonicalBrowsingContext::RestoreState::Resolve() { + MOZ_DIAGNOSTIC_ASSERT(mPromise); + mPromise->MaybeResolveWithUndefined(); + mPromise = nullptr; +} + +nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore( + const nsTArray& aSesssionStorage, uint32_t aEpoch) { + nsCOMPtr funcs = do_ImportESModule( + "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible); + if (!funcs) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr wrapped = do_QueryInterface(funcs); + AutoJSAPI jsapi; + if (!jsapi.Init(wrapped->GetJSObjectGlobal())) { + return NS_ERROR_FAILURE; + } + + JS::Rooted key(jsapi.cx(), Top()->PermanentKey()); + + Record> storage; + JS::Rooted update(jsapi.cx()); + + if (!aSesssionStorage.IsEmpty()) { + SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage, + storage); + if (!ToJSValue(jsapi.cx(), storage, &update)) { + return NS_ERROR_FAILURE; + } + } else { + update.setNull(); + } + + return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this, + key, aEpoch, update); +} + +void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage( + const std::function& aDone) { + if (!StaticPrefs::browser_sessionstore_collect_session_storage_AtStartup()) { + aDone(); + return; + } + + using DataPromise = BackgroundSessionStorageManager::DataPromise; + BackgroundSessionStorageManager::GetData( + this, StaticPrefs::browser_sessionstore_dom_storage_limit(), + /* aClearSessionStoreTimer = */ true) + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()]( + const DataPromise::ResolveOrRejectValue& valueList) { + if (valueList.IsResolve()) { + self->WriteSessionStorageToSessionStore( + valueList.ResolveValue(), epoch); + } + aDone(); + }); +} + +/* static */ +void CanonicalBrowsingContext::UpdateSessionStoreForStorage( + uint64_t aBrowsingContextId) { + RefPtr browsingContext = Get(aBrowsingContextId); + + if (!browsingContext) { + return; + } + + browsingContext->UpdateSessionStoreSessionStorage([]() {}); +} + +void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() { + if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + return; + } + + if (!IsTop()) { + Top()->MaybeScheduleSessionStoreUpdate(); + return; + } + + if (IsInBFCache()) { + return; + } + + if (mSessionStoreSessionStorageUpdateTimer) { + return; + } + + if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) { + auto result = NS_NewTimerWithFuncCallback( + [](nsITimer*, void* aClosure) { + auto* context = static_cast(aClosure); + context->UpdateSessionStoreSessionStorage([]() {}); + }, + this, StaticPrefs::browser_sessionstore_interval(), + nsITimer::TYPE_ONE_SHOT, + "CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate"); + + if (result.isErr()) { + return; + } + + mSessionStoreSessionStorageUpdateTimer = result.unwrap(); + } +} + +void CanonicalBrowsingContext::CancelSessionStoreUpdate() { + if (mSessionStoreSessionStorageUpdateTimer) { + mSessionStoreSessionStorageUpdateTimer->Cancel(); + mSessionStoreSessionStorageUpdateTimer = nullptr; + } +} + +void CanonicalBrowsingContext::SetContainerFeaturePolicy( + FeaturePolicy* aContainerFeaturePolicy) { + mContainerFeaturePolicy = aContainerFeaturePolicy; + + if (WindowGlobalParent* current = GetCurrentWindowGlobal()) { + Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy); + } +} + +void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) { + MOZ_DIAGNOSTIC_ASSERT(IsTopContent()); + MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0, + "Can only set CrossGroupOpenerId once"); + mCrossGroupOpenerId = aOpenerId; +} + +void CanonicalBrowsingContext::SetCrossGroupOpener( + CanonicalBrowsingContext& aCrossGroupOpener, ErrorResult& aRv) { + if (!IsTopContent()) { + aRv.ThrowNotAllowedError( + "Can only set crossGroupOpener on toplevel content"); + return; + } + if (mCrossGroupOpenerId != 0) { + aRv.ThrowNotAllowedError("Can only set crossGroupOpener once"); + return; + } + + SetCrossGroupOpenerId(aCrossGroupOpener.Id()); +} + +auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID) + -> nsTArray::iterator { + return std::find_if( + mUnloadingHosts.begin(), mUnloadingHosts.end(), + [&](const auto& host) { return host.mChildID == aChildID; }); +} + +void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) { + // Notify any callbacks which were waiting for the host to finish unloading + // that it has. + auto found = FindUnloadingHost(aChildID); + if (found != mUnloadingHosts.end()) { + auto callbacks = std::move(found->mCallbacks); + mUnloadingHosts.RemoveElementAt(found); + for (const auto& callback : callbacks) { + callback(); + } + } +} + +void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) { + MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end()); + mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}}); +} + +void CanonicalBrowsingContext::BrowserParentDestroyed( + BrowserParent* aBrowserParent, bool aAbnormalShutdown) { + ClearUnloadingHost(aBrowserParent->Manager()->ChildID()); + + // Handling specific to when the current BrowserParent has been destroyed. + if (mCurrentBrowserParent == aBrowserParent) { + mCurrentBrowserParent = nullptr; + + // If this BrowserParent is for a subframe, attempt to recover from a + // subframe crash by rendering the subframe crashed page in the embedding + // content. + if (aAbnormalShutdown) { + ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent()); + } + } +} + +void CanonicalBrowsingContext::ShowSubframeCrashedUI( + BrowserBridgeParent* aBridge) { + if (!aBridge || IsDiscarded() || !aBridge->CanSend()) { + return; + } + + MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() || + aBridge->GetBrowsingContext() == this); + + // There is no longer a current inner window within this + // BrowsingContext, update the `CurrentInnerWindowId` field to reflect + // this. + MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0)); + + // The owning process will now be the embedder to render the subframe + // crashed page, switch ownership back over. + SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID()); + SetCurrentBrowserParent(aBridge->Manager()); + + Unused << aBridge->SendSubFrameCrashed(); +} + +static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext, + uint32_t aBFCacheCombo, bool aIsSubDoc) { + if (aIsSubDoc) { + nsAutoCString uri("[no uri]"); + nsCOMPtr currentURI = + aBrowsingContext->Canonical()->GetCurrentURI(); + if (currentURI) { + uri = currentURI->GetSpecOrDefault(); + } + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + (" ** Blocked for document %s", uri.get())); + } + if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + (" * event handling suppression")); + } + if (aBFCacheCombo & BFCacheStatus::SUSPENDED) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window")); + } + if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * unload listener")); + } + if (aBFCacheCombo & BFCacheStatus::REQUEST) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup")); + } + if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia")); + } + if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection")); + } + if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content")); + } + if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use")); + } + if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use")); + } + if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR")); + } + if (aBFCacheCombo & BFCacheStatus::BEFOREUNLOAD_LISTENER) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * beforeunload listener")); + } + if (aBFCacheCombo & BFCacheStatus::ACTIVE_LOCK) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * has active Web Locks")); + } +} + +bool CanonicalBrowsingContext::AllowedInBFCache( + const Maybe& aChannelId, nsIURI* aNewURI) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { + nsAutoCString uri("[no uri]"); + nsCOMPtr currentURI = GetCurrentURI(); + if (currentURI) { + uri = currentURI->GetSpecOrDefault(); + } + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get())); + } + + if (IsInProcess()) { + return false; + } + + uint32_t bfcacheCombo = 0; + if (mRestoreState) { + bfcacheCombo |= BFCacheStatus::RESTORING; + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore")); + } + + if (Group()->Toplevels().Length() > 1) { + bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG; + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + (" * auxiliary BrowsingContexts")); + } + + // There are not a lot of about:* pages that are allowed to load in + // subframes, so it's OK to allow those few about:* pages enter BFCache. + MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache"); + + WindowGlobalParent* wgp = GetCurrentWindowGlobal(); + if (wgp && wgp->GetDocumentURI()) { + nsCOMPtr currentURI = wgp->GetDocumentURI(); + // Exempt about:* pages from bfcache, with the exception of about:blank + if (currentURI->SchemeIs("about") && + !currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) { + bfcacheCombo |= BFCacheStatus::ABOUT_PAGE; + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page")); + } + + if (aNewURI) { + bool equalUri = false; + aNewURI->Equals(currentURI, &equalUri); + if (equalUri) { + // When loading the same uri, disable bfcache so that + // nsDocShell::OnNewURI transforms the load to LOAD_NORMAL_REPLACE. + return false; + } + } + } + + // For telemetry we're collecting all the flags for all the BCs hanging + // from this top-level BC. + PreOrderWalk([&](BrowsingContext* aBrowsingContext) { + WindowGlobalParent* wgp = + aBrowsingContext->Canonical()->GetCurrentWindowGlobal(); + uint32_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0; + if (wgp) { + const Maybe& singleChannelId = wgp->GetSingleChannelId(); + if (singleChannelId.isSome()) { + if (singleChannelId.value() == 0 || aChannelId.isNothing() || + singleChannelId.value() != aChannelId.value()) { + subDocBFCacheCombo |= BFCacheStatus::REQUEST; + } + } + } + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { + LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo, + aBrowsingContext != this); + } + + bfcacheCombo |= subDocBFCacheCombo; + }); + + nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo); + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { + nsAutoCString uri("[no uri]"); + nsCOMPtr currentURI = GetCurrentURI(); + if (currentURI) { + uri = currentURI->GetSpecOrDefault(); + } + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + (" +> %s %s be blocked from going into the BFCache", uri.get(), + bfcacheCombo == 0 ? "shouldn't" : "should")); + } + + if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) { + bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER; + } + + return bfcacheCombo == 0; +} + +void CanonicalBrowsingContext::SetTouchEventsOverride( + dom::TouchEventsOverride aOverride, ErrorResult& aRv) { + SetTouchEventsOverrideInternal(aOverride, aRv); +} + +void CanonicalBrowsingContext::SetTargetTopLevelLinkClicksToBlank( + bool aTargetTopLevelLinkClicksToBlank, ErrorResult& aRv) { + SetTargetTopLevelLinkClicksToBlankInternal(aTargetTopLevelLinkClicksToBlank, + aRv); +} + +void CanonicalBrowsingContext::AddPageAwakeRequest() { + MOZ_ASSERT(IsTop()); + auto count = GetPageAwakeRequestCount(); + MOZ_ASSERT(count < UINT32_MAX); + Unused << SetPageAwakeRequestCount(++count); +} + +void CanonicalBrowsingContext::RemovePageAwakeRequest() { + MOZ_ASSERT(IsTop()); + auto count = GetPageAwakeRequestCount(); + MOZ_ASSERT(count > 0); + Unused << SetPageAwakeRequestCount(--count); +} + +void CanonicalBrowsingContext::CloneDocumentTreeInto( + CanonicalBrowsingContext* aSource, const nsACString& aRemoteType, + embedding::PrintData&& aPrintData) { + NavigationIsolationOptions options; + options.mRemoteType = aRemoteType; + + mClonePromise = + ChangeRemoteness(options, /* aPendingSwitchId = */ 0) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [source = MaybeDiscardedBrowsingContext{aSource}, + data = std::move(aPrintData)]( + BrowserParent* aBp) -> RefPtr { + RefPtr bridge = + aBp->GetBrowserBridgeParent(); + return aBp->SendCloneDocumentTreeIntoSelf(source, data) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [bridge]( + BrowserParent::CloneDocumentTreeIntoSelfPromise:: + ResolveOrRejectValue&& aValue) { + // We're cloning a remote iframe, so we created a + // BrowserBridge which makes us register an OOP load + // (see Document::OOPChildLoadStarted), even though + // this isn't a real load. We call + // SendMaybeFireEmbedderLoadEvents here so that we do + // register the end of the load (see + // Document::OOPChildLoadDone). + if (bridge) { + Unused << bridge->SendMaybeFireEmbedderLoadEvents( + EmbedderElementEventType::NoEvent); + } + if (aValue.IsResolve() && aValue.ResolveValue()) { + return GenericNonExclusivePromise::CreateAndResolve( + true, __func__); + } + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + }); + }, + [](nsresult aRv) -> RefPtr { + NS_WARNING( + nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv)) + .get()); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + }); + + mClonePromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}]() { self->mClonePromise = nullptr; }); +} + +bool CanonicalBrowsingContext::StartApzAutoscroll(float aAnchorX, + float aAnchorY, + nsViewID aScrollId, + uint32_t aPresShellId) { + nsCOMPtr widget; + mozilla::layers::LayersId layersId{0}; + + if (IsInProcess()) { + nsCOMPtr outer = GetDOMWindow(); + if (!outer) { + return false; + } + + widget = widget::WidgetUtils::DOMWindowToWidget(outer); + if (widget) { + layersId = widget->GetRootLayerTreeId(); + } + } else { + RefPtr parent = GetBrowserParent(); + if (!parent) { + return false; + } + + widget = parent->GetWidget(); + layersId = parent->GetLayersId(); + } + + if (!widget || !widget->AsyncPanZoomEnabled()) { + return false; + } + + // The anchor coordinates that are passed in are relative to the origin of the + // screen, but we are sending them to APZ which only knows about coordinates + // relative to the widget, so convert them accordingly. + const LayoutDeviceIntPoint anchor = + RoundedToInt(LayoutDevicePoint(aAnchorX, aAnchorY)) - + widget->WidgetToScreenOffset(); + + mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId); + + return widget->StartAsyncAutoscroll( + ViewAs( + anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds), + guid); +} + +void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId, + uint32_t aPresShellId) { + nsCOMPtr widget; + mozilla::layers::LayersId layersId{0}; + + if (IsInProcess()) { + nsCOMPtr outer = GetDOMWindow(); + if (!outer) { + return; + } + + widget = widget::WidgetUtils::DOMWindowToWidget(outer); + if (widget) { + layersId = widget->GetRootLayerTreeId(); + } + } else { + RefPtr parent = GetBrowserParent(); + if (!parent) { + return; + } + + widget = parent->GetWidget(); + layersId = parent->GetLayersId(); + } + + if (!widget || !widget->AsyncPanZoomEnabled()) { + return; + } + + mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId); + widget->StopAsyncAutoscroll(guid); +} + +already_AddRefed +CanonicalBrowsingContext::GetMostRecentLoadingSessionHistoryEntry() { + if (mLoadingEntries.IsEmpty()) { + return nullptr; + } + + RefPtr entry = mLoadingEntries.LastElement().mEntry; + return entry.forget(); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext, + BrowsingContext) + tmp->mPermanentKey.setNull(); + if (tmp->mSessionHistory) { + tmp->mSessionHistory->SetBrowsingContext(nullptr); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy, + mCurrentBrowserParent, mWebProgress, + mSessionStoreSessionStorageUpdateTimer) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext, + BrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy, + mCurrentBrowserParent, mWebProgress, + mSessionStoreSessionStorageUpdateTimer) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext, + BrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext) +NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext) +NS_INTERFACE_MAP_END_INHERITING(BrowsingContext) + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h new file mode 100644 index 0000000000..9420c7e647 --- /dev/null +++ b/docshell/base/CanonicalBrowsingContext.h @@ -0,0 +1,601 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_CanonicalBrowsingContext_h +#define mozilla_dom_CanonicalBrowsingContext_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/MediaControlKeySource.h" +#include "mozilla/dom/BrowsingContextWebProgress.h" +#include "mozilla/dom/ProcessIsolation.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStoreRestoreData.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/RefPtr.h" +#include "mozilla/MozPromise.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsISecureBrowserUI.h" + +class nsIBrowserDOMWindow; +class nsISHistory; +class nsIWidget; +class nsIPrintSettings; +class nsSHistory; +class nsBrowserStatusFilter; +class nsSecureBrowserUI; +class CallerWillNotifyHistoryIndexAndLengthChanges; +class nsITimer; + +namespace mozilla { +enum class CallState; + +namespace embedding { +class PrintData; +} + +namespace net { +class DocumentLoadListener; +} + +namespace dom { + +class BrowserParent; +class BrowserBridgeParent; +class FeaturePolicy; +struct LoadURIOptions; +class MediaController; +struct LoadingSessionHistoryInfo; +class SSCacheCopy; +class WindowGlobalParent; +class SessionStoreFormData; +class SessionStoreScrollData; + +// CanonicalBrowsingContext is a BrowsingContext living in the parent +// process, with whatever extra data that a BrowsingContext in the +// parent needs. +class CanonicalBrowsingContext final : public BrowsingContext { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( + CanonicalBrowsingContext, BrowsingContext) + + static already_AddRefed Get(uint64_t aId); + static CanonicalBrowsingContext* Cast(BrowsingContext* aContext); + static const CanonicalBrowsingContext* Cast(const BrowsingContext* aContext); + static already_AddRefed Cast( + already_AddRefed&& aContext); + + bool IsOwnedByProcess(uint64_t aProcessId) const { + return mProcessId == aProcessId; + } + bool IsEmbeddedInProcess(uint64_t aProcessId) const { + return mEmbedderProcessId == aProcessId; + } + uint64_t OwnerProcessId() const { return mProcessId; } + uint64_t EmbedderProcessId() const { return mEmbedderProcessId; } + ContentParent* GetContentParent() const; + + void GetCurrentRemoteType(nsACString& aRemoteType, ErrorResult& aRv) const; + + void SetOwnerProcessId(uint64_t aProcessId); + + // The ID of the BrowsingContext which caused this BrowsingContext to be + // opened, or `0` if this is unknown. + // Only set for toplevel content BrowsingContexts, and may be from a different + // BrowsingContextGroup. + uint64_t GetCrossGroupOpenerId() const { return mCrossGroupOpenerId; } + void SetCrossGroupOpenerId(uint64_t aOpenerId); + void SetCrossGroupOpener(CanonicalBrowsingContext& aCrossGroupOpener, + ErrorResult& aRv); + + void GetWindowGlobals(nsTArray>& aWindows); + + // The current active WindowGlobal. + WindowGlobalParent* GetCurrentWindowGlobal() const; + + // Same as the methods on `BrowsingContext`, but with the types already cast + // to the parent process type. + CanonicalBrowsingContext* GetParent() { + return Cast(BrowsingContext::GetParent()); + } + CanonicalBrowsingContext* Top() { return Cast(BrowsingContext::Top()); } + WindowGlobalParent* GetParentWindowContext(); + WindowGlobalParent* GetTopWindowContext(); + + already_AddRefed GetParentProcessWidgetContaining(); + already_AddRefed GetBrowserDOMWindow(); + + // Same as `GetParentWindowContext`, but will also cross and + // content/chrome boundaries. + already_AddRefed GetEmbedderWindowGlobal() const; + + already_AddRefed GetParentCrossChromeBoundary(); + + already_AddRefed TopCrossChromeBoundary(); + Nullable GetTopChromeWindow(); + + nsISHistory* GetSessionHistory(); + SessionHistoryEntry* GetActiveSessionHistoryEntry(); + void SetActiveSessionHistoryEntry(SessionHistoryEntry* aEntry); + + UniquePtr CreateLoadingSessionHistoryEntryForLoad( + nsDocShellLoadState* aLoadState, SessionHistoryEntry* aExistingEntry, + nsIChannel* aChannel); + + UniquePtr ReplaceLoadingSessionHistoryEntryForLoad( + LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel); + + using PrintPromise = MozPromise; + MOZ_CAN_RUN_SCRIPT RefPtr Print(nsIPrintSettings*); + MOZ_CAN_RUN_SCRIPT already_AddRefed PrintJS(nsIPrintSettings*, + ErrorResult&); + + // Call the given callback on all top-level descendant BrowsingContexts. + // Return Callstate::Stop from the callback to stop calling + // further children. + void CallOnAllTopDescendants( + const std::function& + aCallback); + + void SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID, + uint32_t aLoadType, bool aPersist, + bool aCloneEntryChildren, bool aChannelExpired, + uint32_t aCacheKey); + + // Calls the session history listeners' OnHistoryReload, storing the result in + // aCanReload. If aCanReload is set to true and we have an active or a loading + // entry then aLoadState will be initialized from that entry, and + // aReloadActiveEntry will be true if we have an active entry. If aCanReload + // is true and aLoadState and aReloadActiveEntry are not set then we should + // attempt to reload based on the current document in the docshell. + void NotifyOnHistoryReload( + bool aForceReload, bool& aCanReload, + Maybe>>& aLoadState, + Maybe& aReloadActiveEntry); + + // See BrowsingContext::SetActiveSessionHistoryEntry. + void SetActiveSessionHistoryEntry(const Maybe& aPreviousScrollPos, + SessionHistoryInfo* aInfo, + uint32_t aLoadType, + uint32_t aUpdatedCacheKey, + const nsID& aChangeID); + + void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo); + + void RemoveDynEntriesFromActiveSessionHistoryEntry(); + + void RemoveFromSessionHistory(const nsID& aChangeID); + + Maybe HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, bool aUserActivation, + Maybe aContentId); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Dispatches a wheel zoom change to the embedder element. + void DispatchWheelZoomChange(bool aIncrease); + + // This function is used to start the autoplay media which are delayed to + // start. If needed, it would also notify the content browsing context which + // are related with the canonical browsing content tree to start delayed + // autoplay media. + void NotifyStartDelayedAutoplayMedia(); + + // This function is used to mute or unmute all media within a tab. It would + // set the media mute property for the top level window and propagate it to + // other top level windows in other processes. + void NotifyMediaMutedChanged(bool aMuted, ErrorResult& aRv); + + // Return the number of unique site origins by iterating all given BCs, + // including their subtrees. + static uint32_t CountSiteOrigins( + GlobalObject& aGlobal, + const Sequence>& aRoots); + + // Return true if a private browsing session is active. + static bool IsPrivateBrowsingActive(); + + // This function would propogate the action to its all child browsing contexts + // in content processes. + void UpdateMediaControlAction(const MediaControlAction& aAction); + + // Triggers a load in the process + using BrowsingContext::LoadURI; + void FixupAndLoadURIString(const nsAString& aURI, + const LoadURIOptions& aOptions, + ErrorResult& aError); + void LoadURI(nsIURI* aURI, const LoadURIOptions& aOptions, + ErrorResult& aError); + + void GoBack(const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction, bool aUserActivation); + void GoForward(const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction, bool aUserActivation); + void GoToIndex(int32_t aIndex, const Optional& aCancelContentJSEpoch, + bool aUserActivation); + void Reload(uint32_t aReloadFlags); + void Stop(uint32_t aStopFlags); + + // Get the publicly exposed current URI loaded in this BrowsingContext. + already_AddRefed GetCurrentURI() const; + void SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI); + + BrowserParent* GetBrowserParent() const; + void SetCurrentBrowserParent(BrowserParent* aBrowserParent); + + // Internal method to change which process a BrowsingContext is being loaded + // in. The returned promise will resolve when the process switch is completed. + // + // A NOT_REMOTE_TYPE aRemoteType argument will perform a process switch into + // the parent process, and the method will resolve with a null BrowserParent. + using RemotenessPromise = MozPromise, nsresult, false>; + RefPtr ChangeRemoteness( + const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId); + + // Return a media controller from the top-level browsing context that can + // control all media belonging to this browsing context tree. Return nullptr + // if the top-level browsing context has been discarded. + MediaController* GetMediaController(); + bool HasCreatedMediaController() const; + + // Attempts to start loading the given load state in this BrowsingContext, + // without requiring any communication from a docshell. This will handle + // computing the right process to load in, and organising handoff to + // the right docshell when we get a response. + bool LoadInParent(nsDocShellLoadState* aLoadState, bool aSetNavigating); + + // Attempts to start loading the given load state in this BrowsingContext, + // in parallel with a DocumentChannelChild being created in the docshell. + // Requires the DocumentChannel to connect with this load for it to + // complete successfully. + bool AttemptSpeculativeLoadInParent(nsDocShellLoadState* aLoadState); + + // Get or create a secure browser UI for this BrowsingContext + nsISecureBrowserUI* GetSecureBrowserUI(); + + BrowsingContextWebProgress* GetWebProgress() { return mWebProgress; } + + // Called when the current URI changes (from an + // nsIWebProgressListener::OnLocationChange event, so that we + // can update our security UI for the new location, or when the + // mixed content/https-only state for our current window is changed. + void UpdateSecurityState(); + + void MaybeAddAsProgressListener(nsIWebProgress* aWebProgress); + + // Called when a navigation forces us to recreate our browsing + // context (for example, when switching in or out of the parent + // process). + // aNewContext is the newly created BrowsingContext that is replacing + // us. + void ReplacedBy(CanonicalBrowsingContext* aNewContext, + const NavigationIsolationOptions& aRemotenessOptions); + + bool HasHistoryEntry(nsISHEntry* aEntry); + bool HasLoadingHistoryEntry(nsISHEntry* aEntry) { + for (const LoadingSessionHistoryEntry& loading : mLoadingEntries) { + if (loading.mEntry == aEntry) { + return true; + } + } + return false; + } + + void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry); + + void AddLoadingSessionHistoryEntry(uint64_t aLoadId, + SessionHistoryEntry* aEntry); + + void GetLoadingSessionHistoryInfoFromParent( + Maybe& aLoadingInfo); + + void HistoryCommitIndexAndLength(); + + void SynchronizeLayoutHistoryState(); + + void ResetScalingZoom(); + + void SetContainerFeaturePolicy(FeaturePolicy* aContainerFeaturePolicy); + FeaturePolicy* GetContainerFeaturePolicy() const { + return mContainerFeaturePolicy; + } + + void SetRestoreData(SessionStoreRestoreData* aData, ErrorResult& aError); + void ClearRestoreState(); + MOZ_CAN_RUN_SCRIPT_BOUNDARY void RequestRestoreTabContent( + WindowGlobalParent* aWindow); + already_AddRefed GetRestorePromise(); + + nsresult WriteSessionStorageToSessionStore( + const nsTArray& aSesssionStorage, uint32_t aEpoch); + + void UpdateSessionStoreSessionStorage(const std::function& aDone); + + static void UpdateSessionStoreForStorage(uint64_t aBrowsingContextId); + + // Called when a BrowserParent for this BrowsingContext has been fully + // destroyed (i.e. `ActorDestroy` was called). + void BrowserParentDestroyed(BrowserParent* aBrowserParent, + bool aAbnormalShutdown); + + void StartUnloadingHost(uint64_t aChildID); + void ClearUnloadingHost(uint64_t aChildID); + + bool AllowedInBFCache(const Maybe& aChannelId, nsIURI* aNewURI); + + // Methods for getting and setting the active state for top level + // browsing contexts, for the process priority manager. + bool IsPriorityActive() const { + MOZ_RELEASE_ASSERT(IsTop()); + return mPriorityActive; + } + void SetPriorityActive(bool aIsActive) { + MOZ_RELEASE_ASSERT(IsTop()); + mPriorityActive = aIsActive; + } + + void SetTouchEventsOverride(dom::TouchEventsOverride, ErrorResult& aRv); + void SetTargetTopLevelLinkClicksToBlank(bool aTargetTopLevelLinkClicksToBlank, + ErrorResult& aRv); + + bool IsReplaced() const { return mIsReplaced; } + + const JS::Heap& PermanentKey() { return mPermanentKey; } + void ClearPermanentKey() { mPermanentKey.setNull(); } + void MaybeSetPermanentKey(Element* aEmbedder); + + // When request for page awake, it would increase a count that is used to + // prevent whole browsing context tree from being suspended. The request can + // be called multiple times. When calling the revoke, it would decrease the + // count and once the count reaches to zero, the browsing context tree could + // be suspended when the tree is inactive. + void AddPageAwakeRequest(); + void RemovePageAwakeRequest(); + + void CloneDocumentTreeInto(CanonicalBrowsingContext* aSource, + const nsACString& aRemoteType, + embedding::PrintData&& aPrintData); + + // Returns a Promise which resolves when cloning documents for printing + // finished if this browsing context is cloning document tree. + RefPtr GetClonePromise() const { + return mClonePromise; + } + + bool StartApzAutoscroll(float aAnchorX, float aAnchorY, nsViewID aScrollId, + uint32_t aPresShellId); + void StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId); + + void AddFinalDiscardListener(std::function&& aListener); + + bool ForceAppWindowActive() const { return mForceAppWindowActive; } + void SetForceAppWindowActive(bool, ErrorResult&); + void RecomputeAppWindowVisibility(); + + already_AddRefed GetMostRecentLoadingSessionHistoryEntry(); + + protected: + // Called when the browsing context is being discarded. + void CanonicalDiscard(); + + // Called when the browsing context is being attached. + void CanonicalAttach(); + + // Called when the browsing context private mode is changed after + // being attached, but before being discarded. + void AdjustPrivateBrowsingCount(bool aPrivateBrowsing); + + using Type = BrowsingContext::Type; + CanonicalBrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, + uint64_t aOwnerProcessId, + uint64_t aEmbedderProcessId, Type aType, + FieldValues&& aInit); + + private: + friend class BrowsingContext; + + virtual ~CanonicalBrowsingContext(); + + class PendingRemotenessChange { + public: + NS_INLINE_DECL_REFCOUNTING(PendingRemotenessChange) + + PendingRemotenessChange(CanonicalBrowsingContext* aTarget, + RemotenessPromise::Private* aPromise, + uint64_t aPendingSwitchId, + const NavigationIsolationOptions& aOptions); + + void Cancel(nsresult aRv); + + private: + friend class CanonicalBrowsingContext; + + ~PendingRemotenessChange(); + void ProcessLaunched(); + void ProcessReady(); + void MaybeFinish(); + void Clear(); + + nsresult FinishTopContent(); + nsresult FinishSubframe(); + + RefPtr mTarget; + RefPtr mPromise; + RefPtr mContentParent; + RefPtr mSpecificGroup; + + bool mProcessReady = false; + bool mWaitingForPrepareToChange = false; + + uint64_t mPendingSwitchId; + NavigationIsolationOptions mOptions; + }; + + struct RestoreState { + NS_INLINE_DECL_REFCOUNTING(RestoreState) + + void ClearData() { mData = nullptr; } + void Resolve(); + + RefPtr mData; + RefPtr mPromise; + uint32_t mRequests = 0; + uint32_t mResolves = 0; + + private: + ~RestoreState() = default; + }; + + friend class net::DocumentLoadListener; + // Called when a DocumentLoadListener is created to start a load for + // this browsing context. Returns false if a higher priority load is + // already in-progress and the new one has been rejected. + bool StartDocumentLoad(net::DocumentLoadListener* aLoad); + // Called once DocumentLoadListener completes handling a load, and it + // is either complete, or handed off to the final channel to deliver + // data to the destination docshell. + // If aContinueNavigating it set, the reference to the DocumentLoadListener + // will be cleared to prevent it being cancelled, however the current load ID + // will be preserved until `EndDocumentLoad` is called again. + void EndDocumentLoad(bool aContinueNavigating); + + bool SupportsLoadingInParent(nsDocShellLoadState* aLoadState, + uint64_t* aOuterWindowId); + + void HistoryCommitIndexAndLength( + const nsID& aChangeID, + const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller); + + struct UnloadingHost { + uint64_t mChildID; + nsTArray> mCallbacks; + }; + nsTArray::iterator FindUnloadingHost(uint64_t aChildID); + + // Called when we want to show the subframe crashed UI as our previous browser + // has become unloaded for one reason or another. + void ShowSubframeCrashedUI(BrowserBridgeParent* aBridge); + + void MaybeScheduleSessionStoreUpdate(); + + void CancelSessionStoreUpdate(); + + void AddPendingDiscard(); + + void RemovePendingDiscard(); + + bool ShouldAddEntryForRefresh(const SessionHistoryEntry* aEntry) { + return ShouldAddEntryForRefresh(aEntry->Info().GetURI(), + aEntry->Info().HasPostData()); + } + bool ShouldAddEntryForRefresh(nsIURI* aNewURI, bool aHasPostData) { + nsCOMPtr currentURI = GetCurrentURI(); + return BrowsingContext::ShouldAddEntryForRefresh(currentURI, aNewURI, + aHasPostData); + } + + already_AddRefed CreateLoadInfo( + SessionHistoryEntry* aEntry); + + // XXX(farre): Store a ContentParent pointer here rather than mProcessId? + // Indicates which process owns the docshell. + uint64_t mProcessId; + + // Indicates which process owns the embedder element. + uint64_t mEmbedderProcessId; + + uint64_t mCrossGroupOpenerId = 0; + + // This function will make the top window context reset its + // "SHEntryHasUserInteraction" cache that prevents documents from repeatedly + // setting user interaction on SH entries. Should be called anytime SH + // entries are added or replaced. + void ResetSHEntryHasUserInteractionCache(); + + RefPtr mCurrentBrowserParent; + + nsTArray mUnloadingHosts; + + // The current URI loaded in this BrowsingContext. This value is only set for + // BrowsingContexts loaded in content processes. + nsCOMPtr mCurrentRemoteURI; + + // The current remoteness change which is in a pending state. + RefPtr mPendingRemotenessChange; + + RefPtr mSessionHistory; + + // Tab media controller is used to control all media existing in the same + // browsing context tree, so it would only exist in the top level browsing + // context. + RefPtr mTabMediaController; + + RefPtr mCurrentLoad; + + struct LoadingSessionHistoryEntry { + uint64_t mLoadId = 0; + RefPtr mEntry; + }; + nsTArray mLoadingEntries; + RefPtr mActiveEntry; + + RefPtr mSecureBrowserUI; + RefPtr mWebProgress; + + nsCOMPtr mDocShellProgressBridge; + RefPtr mStatusFilter; + + RefPtr mContainerFeaturePolicy; + + friend class BrowserSessionStore; + WeakPtr& GetSessionStoreFormDataRef() { + return mFormdata; + } + WeakPtr& GetSessionStoreScrollDataRef() { + return mScroll; + } + + WeakPtr mFormdata; + WeakPtr mScroll; + + RefPtr mRestoreState; + + nsCOMPtr mSessionStoreSessionStorageUpdateTimer; + + // If this is a top level context, this is true if our browser ID is marked as + // active in the process priority manager. + bool mPriorityActive = false; + + // See CanonicalBrowsingContext.forceAppWindowActive. + bool mForceAppWindowActive = false; + + bool mIsReplaced = false; + + // A Promise created when cloning documents for printing. + RefPtr mClonePromise; + + JS::Heap mPermanentKey; + + uint32_t mPendingDiscards = 0; + + bool mFullyDiscarded = false; + + nsTArray> mFullyDiscardedListeners; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_CanonicalBrowsingContext_h) diff --git a/docshell/base/ChildProcessChannelListener.cpp b/docshell/base/ChildProcessChannelListener.cpp new file mode 100644 index 0000000000..628d75abb1 --- /dev/null +++ b/docshell/base/ChildProcessChannelListener.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ChildProcessChannelListener.h" + +#include "mozilla/ipc/Endpoint.h" +#include "nsDocShellLoadState.h" + +namespace mozilla { +namespace dom { + +static StaticRefPtr sCPCLSingleton; + +void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier, + Callback&& aCallback) { + if (auto args = mChannelArgs.Extract(aIdentifier)) { + nsresult rv = + aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints), + args->mTiming); + args->mResolver(rv); + } else { + mCallbacks.InsertOrUpdate(aIdentifier, std::move(aCallback)); + } +} + +void ChildProcessChannelListener::OnChannelReady( + nsDocShellLoadState* aLoadState, uint64_t aIdentifier, + nsTArray&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming, + Resolver&& aResolver) { + if (auto callback = mCallbacks.Extract(aIdentifier)) { + nsresult rv = + (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming); + aResolver(rv); + } else { + mChannelArgs.InsertOrUpdate( + aIdentifier, CallbackArgs{aLoadState, std::move(aStreamFilterEndpoints), + aTiming, std::move(aResolver)}); + } +} + +ChildProcessChannelListener::~ChildProcessChannelListener() { + for (const auto& args : mChannelArgs.Values()) { + args.mResolver(NS_ERROR_FAILURE); + } +} + +already_AddRefed +ChildProcessChannelListener::GetSingleton() { + if (!sCPCLSingleton) { + sCPCLSingleton = new ChildProcessChannelListener(); + ClearOnShutdown(&sCPCLSingleton); + } + RefPtr cpcl = sCPCLSingleton; + return cpcl.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/ChildProcessChannelListener.h b/docshell/base/ChildProcessChannelListener.h new file mode 100644 index 0000000000..c00c2ff5a7 --- /dev/null +++ b/docshell/base/ChildProcessChannelListener.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ChildProcessChannelListener_h +#define mozilla_dom_ChildProcessChannelListener_h + +#include + +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "nsDOMNavigationTiming.h" +#include "nsTHashMap.h" +#include "nsIChannel.h" +#include "mozilla/ipc/BackgroundUtils.h" + +namespace mozilla::dom { + +class ChildProcessChannelListener final { + NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener) + + using Endpoint = mozilla::ipc::Endpoint; + using Resolver = std::function; + using Callback = std::function&&, nsDOMNavigationTiming*)>; + + void RegisterCallback(uint64_t aIdentifier, Callback&& aCallback); + + void OnChannelReady(nsDocShellLoadState* aLoadState, uint64_t aIdentifier, + nsTArray&& aStreamFilterEndpoints, + nsDOMNavigationTiming* aTiming, Resolver&& aResolver); + + static already_AddRefed GetSingleton(); + + private: + ChildProcessChannelListener() = default; + ~ChildProcessChannelListener(); + struct CallbackArgs { + RefPtr mLoadState; + nsTArray mStreamFilterEndpoints; + RefPtr mTiming; + Resolver mResolver; + }; + + // TODO Backtrack. + nsTHashMap mCallbacks; + nsTHashMap mChannelArgs; +}; + +} // namespace mozilla::dom + +#endif // !defined(mozilla_dom_ChildProcessChannelListener_h) diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h new file mode 100644 index 0000000000..594ebdb3bb --- /dev/null +++ b/docshell/base/IHistory.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_IHistory_h_ +#define mozilla_IHistory_h_ + +#include "nsISupports.h" +#include "nsURIHashKey.h" +#include "nsTHashSet.h" +#include "nsTObserverArray.h" + +class nsIURI; +class nsIWidget; + +namespace mozilla { + +namespace dom { +class ContentParent; +class Document; +class Link; +} // namespace dom + +// 0057c9d3-b98e-4933-bdc5-0275d06705e1 +#define IHISTORY_IID \ + { \ + 0x0057c9d3, 0xb98e, 0x4933, { \ + 0xbd, 0xc5, 0x02, 0x75, 0xd0, 0x67, 0x05, 0xe1 \ + } \ + } + +class IHistory : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID) + + using ContentParentSet = nsTHashSet>; + + /** + * Registers the Link for notifications about the visited-ness of aURI. + * Consumers should assume that the URI is unvisited after calling this, and + * they will be notified if that state (unvisited) changes by having + * VisitedQueryFinished called on themselves. Note that it may call + * synchronously if the answer is already known. + * + * @note VisitedQueryFinished must not call RegisterVisitedCallback or + * UnregisterVisitedCallback. + * + * @pre aURI must not be null. + * @pre aLink may be null only in the parent (chrome) process. + * + * @param aURI + * The URI to check. + * @param aLink + * The link to update whenever the history status changes. The + * implementation will only hold onto a raw pointer, so if this + * object should be destroyed, be sure to call + * UnregisterVistedCallback first. + */ + virtual void RegisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + /** + * Schedules a single visited query from a given child process. + * + * @param aURI the URI to query. + * @param ContentParent the process interested in knowing about the visited + * state of this URI. + */ + virtual void ScheduleVisitedQuery(nsIURI* aURI, dom::ContentParent*) = 0; + + /** + * Unregisters a previously registered Link object. This must be called + * before destroying the registered object, and asserts when misused. + * + * @pre aURI must not be null. + * @pre aLink must not be null. + * + * @param aURI + * The URI that aLink was registered for. + * @param aLink + * The link object to unregister for aURI. + */ + virtual void UnregisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + enum class VisitedStatus : uint8_t { + Unknown, + Visited, + Unvisited, + }; + + /** + * Notifies about the visited status of a given URI. The visited status cannot + * be unknown, otherwise there's no point in notifying of anything. + * + * @param ContentParentSet a set of content processes that are interested on + * this change. If null, it is broadcasted to all + * child processes. + */ + virtual void NotifyVisited(nsIURI*, VisitedStatus, + const ContentParentSet* = nullptr) = 0; + + enum VisitFlags { + /** + * Indicates whether the URI was loaded in a top-level window. + */ + TOP_LEVEL = 1 << 0, + /** + * Indicates whether the URI is the target of a permanent redirect. + */ + REDIRECT_PERMANENT = 1 << 1, + /** + * Indicates whether the URI is the target of a temporary redirect. + */ + REDIRECT_TEMPORARY = 1 << 2, + /** + * Indicates the URI will redirect (Response code 3xx). + */ + REDIRECT_SOURCE = 1 << 3, + /** + * Indicates the URI caused an error that is unlikely fixable by a + * retry, like a not found or unfetchable page. + */ + UNRECOVERABLE_ERROR = 1 << 4, + /** + * If REDIRECT_SOURCE is set, this indicates that the redirect is permanent. + * Note this differs from REDIRECT_PERMANENT because that one refers to how + * we reached the URI, while this is used when the URI itself redirects. + */ + REDIRECT_SOURCE_PERMANENT = 1 << 5 + }; + + /** + * Adds a history visit for the URI. + * + * @pre aURI must not be null. + * + * @param aWidget + * The widget for the DocShell. + * @param aURI + * The URI of the page being visited. + * @param aLastVisitedURI + * The URI of the last visit in the chain. + * @param aFlags + * The VisitFlags describing this visit. + * @param aBrowserId + * The id of browser used for this visit. + */ + NS_IMETHOD VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI, + uint32_t aFlags, uint64_t aBrowserId) = 0; + + /** + * Set the title of the URI. + * + * @pre aURI must not be null. + * + * @param aURI + * The URI to set the title for. + * @param aTitle + * The title string. + */ + NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID) + +} // namespace mozilla + +#endif // mozilla_IHistory_h_ diff --git a/docshell/base/LoadContext.cpp b/docshell/base/LoadContext.cpp new file mode 100644 index 0000000000..2e501be221 --- /dev/null +++ b/docshell/base/LoadContext.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI +#include "mozilla/dom/BrowsingContext.h" +#include "nsContentUtils.h" +#include "xpcpublic.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LoadContext, nsILoadContext, nsIInterfaceRequestor) + +LoadContext::LoadContext(const IPC::SerializedLoadContext& aToCopy, + dom::Element* aTopFrameElement, + OriginAttributes& aAttrs) + : mTopFrameElement(do_GetWeakReference(aTopFrameElement)), + mIsContent(aToCopy.mIsContent), + mUseRemoteTabs(aToCopy.mUseRemoteTabs), + mUseRemoteSubframes(aToCopy.mUseRemoteSubframes), + mUseTrackingProtection(aToCopy.mUseTrackingProtection), +#ifdef DEBUG + mIsNotNull(aToCopy.mIsNotNull), +#endif + mOriginAttributes(aAttrs) { +} + +LoadContext::LoadContext(OriginAttributes& aAttrs) + : mTopFrameElement(nullptr), + mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false), +#ifdef DEBUG + mIsNotNull(true), +#endif + mOriginAttributes(aAttrs) { +} + +LoadContext::LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase) + : mTopFrameElement(nullptr), + mIsContent(true), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false), +#ifdef DEBUG + mIsNotNull(true), +#endif + mOriginAttributes(aPrincipal->OriginAttributesRef()) { + if (!aOptionalBase) { + return; + } + + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetIsContent(&mIsContent)); + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetUseRemoteTabs(&mUseRemoteTabs)); + MOZ_ALWAYS_SUCCEEDS( + aOptionalBase->GetUseRemoteSubframes(&mUseRemoteSubframes)); + MOZ_ALWAYS_SUCCEEDS( + aOptionalBase->GetUseTrackingProtection(&mUseTrackingProtection)); +} + +LoadContext::~LoadContext() = default; + +//----------------------------------------------------------------------------- +// LoadContext::nsILoadContext +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +LoadContext::GetAssociatedWindow(mozIDOMWindowProxy**) { + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopWindow(mozIDOMWindowProxy**) { + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopFrameElement(dom::Element** aElement) { + nsCOMPtr element = do_QueryReferent(mTopFrameElement); + element.forget(aElement); + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetIsContent(bool* aIsContent) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aIsContent); + + *aIsContent = mIsContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + + *aUsePrivateBrowsing = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::SetPrivateBrowsing(bool aUsePrivateBrowsing) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetUseRemoteTabs(bool* aUseRemoteTabs) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseRemoteTabs); + + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetRemoteTabs(bool aUseRemoteTabs) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetUseRemoteSubframes(bool* aUseRemoteSubframes) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseRemoteSubframes); + + *aUseRemoteSubframes = mUseRemoteSubframes; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetRemoteSubframes(bool aUseRemoteSubframes) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle aAttrs) { + bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP_(void) +LoadContext::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) { + aAttrs = mOriginAttributes; +} + +NS_IMETHODIMP +LoadContext::GetUseTrackingProtection(bool* aUseTrackingProtection) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseTrackingProtection); + + *aUseTrackingProtection = mUseTrackingProtection; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetUseTrackingProtection(bool aUseTrackingProtection) { + MOZ_ASSERT_UNREACHABLE("Should only be set through nsDocShell"); + + return NS_ERROR_UNEXPECTED; +} + +//----------------------------------------------------------------------------- +// LoadContext::nsIInterfaceRequestor +//----------------------------------------------------------------------------- +NS_IMETHODIMP +LoadContext::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (aIID.Equals(NS_GET_IID(nsILoadContext))) { + *aResult = static_cast(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return NS_NOINTERFACE; +} + +static already_AddRefed CreateInstance(bool aPrivate) { + OriginAttributes oa; + oa.mPrivateBrowsingId = aPrivate ? 1 : 0; + + nsCOMPtr lc = new LoadContext(oa); + + return lc.forget(); +} + +already_AddRefed CreateLoadContext() { + return CreateInstance(false); +} + +already_AddRefed CreatePrivateLoadContext() { + return CreateInstance(true); +} + +} // namespace mozilla diff --git a/docshell/base/LoadContext.h b/docshell/base/LoadContext.h new file mode 100644 index 0000000000..5cb71ff347 --- /dev/null +++ b/docshell/base/LoadContext.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LoadContext_h +#define LoadContext_h + +#include "SerializedLoadContext.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadContext.h" + +namespace mozilla::dom { +class Element; +} + +namespace mozilla { + +/** + * Class that provides nsILoadContext info in Parent process. Typically copied + * from Child via SerializedLoadContext. + * + * Note: this is not the "normal" or "original" nsILoadContext. That is + * typically provided by BrowsingContext. This is only used when the original + * docshell is in a different process and we need to copy certain values from + * it. + */ + +class LoadContext final : public nsILoadContext, public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADCONTEXT + NS_DECL_NSIINTERFACEREQUESTOR + + LoadContext(const IPC::SerializedLoadContext& aToCopy, + dom::Element* aTopFrameElement, OriginAttributes& aAttrs); + + // Constructor taking reserved origin attributes. + explicit LoadContext(OriginAttributes& aAttrs); + + // Constructor for creating a LoadContext with a given browser flag. + explicit LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase = nullptr); + + private: + ~LoadContext(); + + nsWeakPtr mTopFrameElement; + bool mIsContent; + bool mUseRemoteTabs; + bool mUseRemoteSubframes; + bool mUseTrackingProtection; +#ifdef DEBUG + bool mIsNotNull; +#endif + OriginAttributes mOriginAttributes; +}; + +already_AddRefed CreateLoadContext(); +already_AddRefed CreatePrivateLoadContext(); + +} // namespace mozilla + +#endif // LoadContext_h diff --git a/docshell/base/SerializedLoadContext.cpp b/docshell/base/SerializedLoadContext.cpp new file mode 100644 index 0000000000..cb598b43fe --- /dev/null +++ b/docshell/base/SerializedLoadContext.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SerializedLoadContext.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsIWebSocketChannel.h" + +namespace IPC { + +SerializedLoadContext::SerializedLoadContext(nsILoadContext* aLoadContext) + : mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + Init(aLoadContext); +} + +SerializedLoadContext::SerializedLoadContext(nsIChannel* aChannel) + : mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + if (!aChannel) { + Init(nullptr); + return; + } + + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + Init(loadContext); + + if (!loadContext) { + // Attempt to retrieve the private bit from the channel if it has been + // overriden. + bool isPrivate = false; + bool isOverriden = false; + nsCOMPtr pbChannel = do_QueryInterface(aChannel); + if (pbChannel && + NS_SUCCEEDED( + pbChannel->IsPrivateModeOverriden(&isPrivate, &isOverriden)) && + isOverriden) { + mIsPrivateBitValid = true; + } + mOriginAttributes.SyncAttributesWithPrivateBrowsing(isPrivate); + } +} + +SerializedLoadContext::SerializedLoadContext(nsIWebSocketChannel* aChannel) + : mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + nsCOMPtr loadContext; + if (aChannel) { + NS_QueryNotificationCallbacks(aChannel, loadContext); + } + Init(loadContext); +} + +void SerializedLoadContext::Init(nsILoadContext* aLoadContext) { + if (aLoadContext) { + mIsNotNull = true; + mIsPrivateBitValid = true; + aLoadContext->GetIsContent(&mIsContent); + aLoadContext->GetUseRemoteTabs(&mUseRemoteTabs); + aLoadContext->GetUseRemoteSubframes(&mUseRemoteSubframes); + aLoadContext->GetUseTrackingProtection(&mUseTrackingProtection); + aLoadContext->GetOriginAttributes(mOriginAttributes); + } else { + mIsNotNull = false; + mIsPrivateBitValid = false; + // none of below values really matter when mIsNotNull == false: + // we won't be GetInterfaced to nsILoadContext + mIsContent = true; + mUseRemoteTabs = false; + mUseRemoteSubframes = false; + mUseTrackingProtection = false; + } +} + +} // namespace IPC diff --git a/docshell/base/SerializedLoadContext.h b/docshell/base/SerializedLoadContext.h new file mode 100644 index 0000000000..d47ad08003 --- /dev/null +++ b/docshell/base/SerializedLoadContext.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SerializedLoadContext_h +#define SerializedLoadContext_h + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/BasePrincipal.h" + +class nsILoadContext; + +/* + * This file contains the IPC::SerializedLoadContext class, which is used to + * copy data across IPDL from Child process contexts so it is available in the + * Parent. + */ + +class nsIChannel; +class nsIWebSocketChannel; + +namespace IPC { + +class SerializedLoadContext { + public: + SerializedLoadContext() + : mIsNotNull(false), + mIsPrivateBitValid(false), + mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + Init(nullptr); + } + + explicit SerializedLoadContext(nsILoadContext* aLoadContext); + explicit SerializedLoadContext(nsIChannel* aChannel); + explicit SerializedLoadContext(nsIWebSocketChannel* aChannel); + + void Init(nsILoadContext* aLoadContext); + + bool IsNotNull() const { return mIsNotNull; } + bool IsPrivateBitValid() const { return mIsPrivateBitValid; } + + // used to indicate if child-side LoadContext * was null. + bool mIsNotNull; + // used to indicate if child-side mUsePrivateBrowsing flag is valid, even if + // mIsNotNull is false, i.e., child LoadContext was null. + bool mIsPrivateBitValid; + bool mIsContent; + bool mUseRemoteTabs; + bool mUseRemoteSubframes; + bool mUseTrackingProtection; + mozilla::OriginAttributes mOriginAttributes; +}; + +// Function to serialize over IPDL +template <> +struct ParamTraits { + typedef SerializedLoadContext paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + nsAutoCString suffix; + aParam.mOriginAttributes.CreateSuffix(suffix); + + WriteParam(aWriter, aParam.mIsNotNull); + WriteParam(aWriter, aParam.mIsContent); + WriteParam(aWriter, aParam.mIsPrivateBitValid); + WriteParam(aWriter, aParam.mUseRemoteTabs); + WriteParam(aWriter, aParam.mUseRemoteSubframes); + WriteParam(aWriter, aParam.mUseTrackingProtection); + WriteParam(aWriter, suffix); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + nsAutoCString suffix; + if (!ReadParam(aReader, &aResult->mIsNotNull) || + !ReadParam(aReader, &aResult->mIsContent) || + !ReadParam(aReader, &aResult->mIsPrivateBitValid) || + !ReadParam(aReader, &aResult->mUseRemoteTabs) || + !ReadParam(aReader, &aResult->mUseRemoteSubframes) || + !ReadParam(aReader, &aResult->mUseTrackingProtection) || + !ReadParam(aReader, &suffix)) { + return false; + } + return aResult->mOriginAttributes.PopulateFromSuffix(suffix); + } +}; + +} // namespace IPC + +#endif // SerializedLoadContext_h diff --git a/docshell/base/SyncedContext.h b/docshell/base/SyncedContext.h new file mode 100644 index 0000000000..679e07edc2 --- /dev/null +++ b/docshell/base/SyncedContext.h @@ -0,0 +1,402 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SyncedContext_h +#define mozilla_dom_SyncedContext_h + +#include +#include +#include +#include "mozilla/Attributes.h" +#include "mozilla/BitSet.h" +#include "mozilla/EnumSet.h" +#include "nsStringFwd.h" +#include "nscore.h" + +// Referenced via macro definitions +#include "mozilla/ErrorResult.h" + +class PickleIterator; + +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC + +namespace mozilla { +namespace ipc { +class IProtocol; +class IPCResult; +template +struct IPDLParamTraits; +} // namespace ipc + +namespace dom { +class ContentParent; +class ContentChild; +template +class MaybeDiscarded; + +namespace syncedcontext { + +template +using Index = typename std::integral_constant; + +// We're going to use the empty base optimization for synced fields of different +// sizes, so we define an empty class for that purpose. +template +struct Empty {}; + +// A templated container for a synced field. I is the index and T is the type. +template +struct Field { + T mField{}; +}; + +// SizedField is a Field with a helper to define either an "empty" field, or a +// field of a given type. +template +using SizedField = std::conditional_t<((sizeof(T) > 8) ? 8 : sizeof(T)) == S, + Field, Empty>; + +template +class Transaction { + public: + using IndexSet = EnumSet>; + + // Set a field at the given index in this `Transaction`. Creating a + // `Transaction` object and setting multiple fields on it allows for + // multiple mutations to be performed atomically. + template + void Set(U&& aValue) { + mValues.Get(Index{}) = std::forward(aValue); + mModified += I; + } + + // Apply the changes from this transaction to the specified Context in all + // processes. This method will call the correct `CanSet` and `DidSet` methods, + // as well as move the value. + // + // If the target has been discarded, changes will be ignored. + // + // NOTE: This method mutates `this`, clearing the modified field set. + [[nodiscard]] nsresult Commit(Context* aOwner); + + // Called from `ContentParent` in response to a transaction from content. + mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded& aOwner, + ContentParent* aSource); + + // Called from `ContentChild` in response to a transaction from the parent. + mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded& aOwner, + uint64_t aEpoch, ContentChild* aSource); + + // Apply the changes from this transaction to the specified Context WITHOUT + // syncing the changes to other processes. + // + // Unlike `Commit`, this method will NOT call the corresponding `CanSet` or + // `DidSet` methods, and can be performed when the target context is + // unattached or discarded. + // + // NOTE: YOU PROBABLY DO NOT WANT TO USE THIS METHOD + void CommitWithoutSyncing(Context* aOwner); + + private: + friend struct mozilla::ipc::IPDLParamTraits>; + + void Write(IPC::MessageWriter* aWriter, + mozilla::ipc::IProtocol* aActor) const; + bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor); + + // You probably don't want to directly call this method - instead call + // `Commit`, which will perform the necessary synchronization. + // + // `Validate` must be called before calling this method. + void Apply(Context* aOwner, bool aFromIPC); + + // Returns the set of fields which failed to validate, or an empty set if + // there were no validation errors. + // + // NOTE: This method mutates `this` if any changes were reverted. + IndexSet Validate(Context* aOwner, ContentParent* aSource); + + template + static void EachIndex(F&& aCallback) { + Context::FieldValues::EachIndex(aCallback); + } + + template + static uint64_t& FieldEpoch(Index, Context* aContext) { + return std::get(aContext->mFields.mEpochs); + } + + typename Context::FieldValues mValues; + IndexSet mModified; +}; + +template +class FieldValues : public Base { + public: + // The number of fields stored by this type. + static constexpr size_t count = Count; + + // The base type will define a series of `Get` methods for looking up a field + // by its field index. + using Base::Get; + + // Calls a generic lambda with an `Index` for each index less than the + // field count. + template + static void EachIndex(F&& aCallback) { + EachIndexInner(std::make_index_sequence(), + std::forward(aCallback)); + } + + private: + friend struct mozilla::ipc::IPDLParamTraits>; + + void Write(IPC::MessageWriter* aWriter, + mozilla::ipc::IProtocol* aActor) const; + bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor); + + template + static void EachIndexInner(std::index_sequence aIndexes, + F&& aCallback) { + (aCallback(Index()), ...); + } +}; + +// Storage related to synchronized context fields. Contains both a tuple of +// individual field values, and epoch information for field synchronization. +template +class FieldStorage { + public: + // Unsafely grab a reference directly to the internal values structure which + // can be modified without telling other processes about the change. + // + // This is only sound in specific code which is already messaging other + // processes, and doesn't need to worry about epochs or other properties of + // field synchronization. + Values& RawValues() { return mValues; } + const Values& RawValues() const { return mValues; } + + // Get an individual field by index. + template + const auto& Get() const { + return RawValues().Get(Index{}); + } + + // Set the value of a field without telling other processes about the change. + // + // This is only sound in specific code which is already messaging other + // processes, and doesn't need to worry about epochs or other properties of + // field synchronization. + template + void SetWithoutSyncing(U&& aValue) { + GetNonSyncingReference() = std::move(aValue); + } + + // Get a reference to a field that can modify without telling other + // processes about the change. + // + // This is only sound in specific code which is already messaging other + // processes, and doesn't need to worry about epochs or other properties of + // field synchronization. + template + auto& GetNonSyncingReference() { + return RawValues().Get(Index{}); + } + + FieldStorage() = default; + explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {} + + private: + template + friend class Transaction; + + // Data Members + std::array mEpochs{}; + Values mValues; +}; + +// Alternative return type enum for `CanSet` validators which allows specifying +// more behaviour. +enum class CanSetResult : uint8_t { + // The set attempt is denied. This is equivalent to returning `false`. + Deny, + // The set attempt is allowed. This is equivalent to returning `true`. + Allow, + // The set attempt is reverted non-fatally. + Revert, +}; + +// Helper type traits to use concrete types rather than generic forwarding +// references for the `SetXXX` methods defined on the synced context type. +// +// This helps avoid potential issues where someone accidentally declares an +// overload of these methods with slightly different types and different +// behaviours. See bug 1659520. +template +struct GetFieldSetterType { + using SetterArg = T; +}; +template <> +struct GetFieldSetterType { + using SetterArg = const nsAString&; +}; +template <> +struct GetFieldSetterType { + using SetterArg = const nsACString&; +}; +template +using FieldSetterType = typename GetFieldSetterType::SetterArg; + +#define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name, + +#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \ + const type& Get##name() const { return mFields.template Get(); } \ + \ + [[nodiscard]] nsresult Set##name( \ + ::mozilla::dom::syncedcontext::FieldSetterType aValue) { \ + Transaction txn; \ + txn.template Set(std::move(aValue)); \ + return txn.Commit(this); \ + } \ + void Set##name(::mozilla::dom::syncedcontext::FieldSetterType aValue, \ + ErrorResult& aRv) { \ + nsresult rv = this->Set##name(std::move(aValue)); \ + if (NS_FAILED(rv)) { \ + aRv.ThrowInvalidStateError("cannot set synced field '" #name \ + "': context is discarded"); \ + } \ + } + +#define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type) \ + template \ + void Set##name(U&& aValue) { \ + this->template Set(std::forward(aValue)); \ + } +#define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \ + case IDX_##name: \ + return #name; + +#define MOZ_DECL_SYNCED_FIELD_INHERIT(name, type) \ + public \ + syncedcontext::SizedField, + +#define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \ + type& Get(FieldIndex) { \ + return Field::mField; \ + } \ + const type& Get(FieldIndex) const { \ + return Field::mField; \ + } + +// Declare a type as a synced context type. +// +// clazz is the name of the type being declared, and `eachfield` is a macro +// which, when called with the name of the macro, will call that macro once for +// each field in the synced context. +#define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield) \ + public: \ + /* Index constants for referring to each field in generic code */ \ + enum FieldIndexes { \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) SYNCED_FIELD_COUNT \ + }; \ + \ + /* Helper for overloading methods like `CanSet` and `DidSet` */ \ + template \ + using FieldIndex = typename ::mozilla::dom::syncedcontext::Index; \ + \ + /* Fields contain all synced fields defined by \ + * `eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT)`, but only those where the size \ + * of the field is equal to size Size will be present. We use SizedField to \ + * remove fields of the wrong size. */ \ + template \ + struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \ + syncedcontext::Empty {}; \ + \ + /* Struct containing the data for all synced fields as members. We filter \ + * sizes to lay out fields of size 1, then 2, then 4 and last 8 or greater. \ + * This way we don't need to consider packing when defining fields, but \ + * we'll just reorder them here. \ + */ \ + struct BaseFieldValues : public Fields<1>, \ + public Fields<2>, \ + public Fields<4>, \ + public Fields<8> { \ + template \ + auto& Get() { \ + return Get(FieldIndex{}); \ + } \ + template \ + const auto& Get() const { \ + return Get(FieldIndex{}); \ + } \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \ + }; \ + using FieldValues = \ + typename ::mozilla::dom::syncedcontext::FieldValues; \ + \ + protected: \ + friend class ::mozilla::dom::syncedcontext::Transaction; \ + ::mozilla::dom::syncedcontext::FieldStorage mFields; \ + \ + public: \ + /* Transaction types for bulk mutations */ \ + using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction; \ + class Transaction final : public BaseTransaction { \ + public: \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET) \ + }; \ + \ + /* Field name getter by field index */ \ + static const char* FieldIndexToName(size_t aIndex) { \ + switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) } \ + return ""; \ + } \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET) + +} // namespace syncedcontext +} // namespace dom + +namespace ipc { + +template +struct IPDLParamTraits> { + typedef dom::syncedcontext::Transaction paramType; + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const paramType& aParam) { + aParam.Write(aWriter, aActor); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + paramType* aResult) { + return aResult->Read(aReader, aActor); + } +}; + +template +struct IPDLParamTraits> { + typedef dom::syncedcontext::FieldValues paramType; + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const paramType& aParam) { + aParam.Write(aWriter, aActor); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + paramType* aResult) { + return aResult->Read(aReader, aActor); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_SyncedContext_h) diff --git a/docshell/base/SyncedContextInlines.h b/docshell/base/SyncedContextInlines.h new file mode 100644 index 0000000000..99141d630d --- /dev/null +++ b/docshell/base/SyncedContextInlines.h @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SyncedContextInlines_h +#define mozilla_dom_SyncedContextInlines_h + +#include "mozilla/dom/SyncedContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "nsReadableUtils.h" +#include "mozilla/HalIPCUtils.h" + +namespace mozilla { +namespace dom { +namespace syncedcontext { + +template +struct IsMozillaMaybe : std::false_type {}; +template +struct IsMozillaMaybe> : std::true_type {}; + +// A super-sketchy logging only function for generating a human-readable version +// of the value of a synced context field. +template +void FormatFieldValue(nsACString& aStr, const T& aValue) { + if constexpr (std::is_same_v) { + if (aValue) { + aStr.AppendLiteral("true"); + } else { + aStr.AppendLiteral("false"); + } + } else if constexpr (std::is_same_v) { + aStr.Append(nsIDToCString(aValue).get()); + } else if constexpr (std::is_integral_v) { + aStr.AppendInt(aValue); + } else if constexpr (std::is_enum_v) { + aStr.AppendInt(static_cast>(aValue)); + } else if constexpr (std::is_floating_point_v) { + aStr.AppendFloat(aValue); + } else if constexpr (std::is_base_of_v) { + AppendUTF16toUTF8(aValue, aStr); + } else if constexpr (std::is_base_of_v) { + aStr.Append(aValue); + } else if constexpr (IsMozillaMaybe::value) { + if (aValue) { + aStr.AppendLiteral("Some("); + FormatFieldValue(aStr, aValue.ref()); + aStr.AppendLiteral(")"); + } else { + aStr.AppendLiteral("Nothing"); + } + } else { + aStr.AppendLiteral("???"); + } +} + +// Sketchy logging-only logic to generate a human-readable output of the actions +// a synced context transaction is going to perform. +template +nsAutoCString FormatTransaction( + typename Transaction::IndexSet aModified, + const typename Context::FieldValues& aOldValues, + const typename Context::FieldValues& aNewValues) { + nsAutoCString result; + Context::FieldValues::EachIndex([&](auto idx) { + if (aModified.contains(idx)) { + result.Append(Context::FieldIndexToName(idx)); + result.AppendLiteral("("); + FormatFieldValue(result, aOldValues.Get(idx)); + result.AppendLiteral("->"); + FormatFieldValue(result, aNewValues.Get(idx)); + result.AppendLiteral(") "); + } + }); + return result; +} + +template +nsCString FormatValidationError( + typename Transaction::IndexSet aFailedFields, const char* prefix) { + MOZ_ASSERT(!aFailedFields.isEmpty()); + return nsDependentCString{prefix} + + StringJoin(", "_ns, aFailedFields, + [](nsACString& dest, const auto& idx) { + dest.Append(Context::FieldIndexToName(idx)); + }); +} + +template +nsresult Transaction::Commit(Context* aOwner) { + if (NS_WARN_IF(aOwner->IsDiscarded())) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + IndexSet failedFields = Validate(aOwner, nullptr); + if (!failedFields.isEmpty()) { + nsCString error = FormatValidationError( + failedFields, "CanSet failed for field(s): "); + MOZ_CRASH_UNSAFE_PRINTF("%s", error.get()); + } + + if (mModified.isEmpty()) { + return NS_OK; + } + + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + + // Increment the field epoch for fields affected by this transaction. + uint64_t epoch = cc->NextBrowsingContextFieldEpoch(); + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + FieldEpoch(idx, aOwner) = epoch; + } + }); + + // Tell our derived class to send the correct "Commit" IPC message. + aOwner->SendCommitTransaction(cc, *this, epoch); + } else { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + // Tell our derived class to send the correct "Commit" IPC messages. + BrowsingContextGroup* group = aOwner->Group(); + group->EachParent([&](ContentParent* aParent) { + aOwner->SendCommitTransaction(aParent, *this, + aParent->GetBrowsingContextFieldEpoch()); + }); + } + + Apply(aOwner, /* aFromIPC */ false); + return NS_OK; +} + +template +mozilla::ipc::IPCResult Transaction::CommitFromIPC( + const MaybeDiscarded& aOwner, ContentParent* aSource) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + if (aOwner.IsNullOrDiscarded()) { + MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug, + ("IPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + Context* owner = aOwner.get(); + + // Validate that the set from content is allowed before continuing. + IndexSet failedFields = Validate(owner, aSource); + if (!failedFields.isEmpty()) { + nsCString error = FormatValidationError( + failedFields, + "Invalid Transaction from Child - CanSet failed for field(s): "); + return IPC_FAIL(aSource, error.get()); + } + + // Validate may have dropped some fields from the transaction, check it's not + // empty before continuing. + if (mModified.isEmpty()) { + return IPC_OK(); + } + + BrowsingContextGroup* group = owner->Group(); + group->EachOtherParent(aSource, [&](ContentParent* aParent) { + owner->SendCommitTransaction(aParent, *this, + aParent->GetBrowsingContextFieldEpoch()); + }); + + Apply(owner, /* aFromIPC */ true); + return IPC_OK(); +} + +template +mozilla::ipc::IPCResult Transaction::CommitFromIPC( + const MaybeDiscarded& aOwner, uint64_t aEpoch, + ContentChild* aSource) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + if (aOwner.IsNullOrDiscarded()) { + MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + Context* owner = aOwner.get(); + + // Clear any fields which have been obsoleted by the epoch. + EachIndex([&](auto idx) { + if (mModified.contains(idx) && FieldEpoch(idx, owner) > aEpoch) { + MOZ_LOG( + Context::GetSyncLog(), LogLevel::Debug, + ("Transaction::Obsoleted(#%" PRIx64 ", %" PRIu64 ">%" PRIu64 "): %s", + owner->Id(), FieldEpoch(idx, owner), aEpoch, + Context::FieldIndexToName(idx))); + mModified -= idx; + } + }); + + if (mModified.isEmpty()) { + return IPC_OK(); + } + + Apply(owner, /* aFromIPC */ true); + return IPC_OK(); +} + +template +void Transaction::Apply(Context* aOwner, bool aFromIPC) { + MOZ_ASSERT(!mModified.isEmpty()); + + MOZ_LOG( + Context::GetSyncLog(), LogLevel::Debug, + ("Transaction::Apply(#%" PRIx64 ", %s): %s", aOwner->Id(), + aFromIPC ? "ipc" : "local", + FormatTransaction(mModified, aOwner->mFields.mValues, mValues) + .get())); + + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + auto& txnField = mValues.Get(idx); + auto& ownerField = aOwner->mFields.mValues.Get(idx); + std::swap(ownerField, txnField); + aOwner->DidSet(idx); + aOwner->DidSet(idx, std::move(txnField)); + } + }); + mModified.clear(); +} + +template +void Transaction::CommitWithoutSyncing(Context* aOwner) { + MOZ_LOG( + Context::GetSyncLog(), LogLevel::Debug, + ("Transaction::CommitWithoutSyncing(#%" PRIx64 "): %s", aOwner->Id(), + FormatTransaction(mModified, aOwner->mFields.mValues, mValues) + .get())); + + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + aOwner->mFields.mValues.Get(idx) = std::move(mValues.Get(idx)); + } + }); + mModified.clear(); +} + +inline CanSetResult AsCanSetResult(CanSetResult aValue) { return aValue; } +inline CanSetResult AsCanSetResult(bool aValue) { + return aValue ? CanSetResult::Allow : CanSetResult::Deny; +} + +template +typename Transaction::IndexSet Transaction::Validate( + Context* aOwner, ContentParent* aSource) { + IndexSet failedFields; + Transaction revertTxn; + + EachIndex([&](auto idx) { + if (!mModified.contains(idx)) { + return; + } + + switch (AsCanSetResult(aOwner->CanSet(idx, mValues.Get(idx), aSource))) { + case CanSetResult::Allow: + break; + case CanSetResult::Deny: + failedFields += idx; + break; + case CanSetResult::Revert: + revertTxn.mValues.Get(idx) = aOwner->mFields.mValues.Get(idx); + revertTxn.mModified += idx; + break; + } + }); + + // If any changes need to be reverted, log them, remove them from this + // transaction, and optionally send a message to the source process. + if (!revertTxn.mModified.isEmpty()) { + // NOTE: Logging with modified IndexSet from revert transaction, and values + // from this transaction, so we log the failed values we're going to revert. + MOZ_LOG( + Context::GetSyncLog(), LogLevel::Debug, + ("Transaction::PartialRevert(#%" PRIx64 ", pid %" PRIPID "): %s", + aOwner->Id(), aSource ? aSource->OtherPid() : base::kInvalidProcessId, + FormatTransaction(revertTxn.mModified, mValues, + revertTxn.mValues) + .get())); + + mModified -= revertTxn.mModified; + + if (aSource) { + aOwner->SendCommitTransaction(aSource, revertTxn, + aSource->GetBrowsingContextFieldEpoch()); + } + } + return failedFields; +} + +template +void Transaction::Write(IPC::MessageWriter* aWriter, + mozilla::ipc::IProtocol* aActor) const { + // Record which field indices will be included, and then write those fields + // out. + typename IndexSet::serializedType modified = mModified.serialize(); + WriteIPDLParam(aWriter, aActor, modified); + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + WriteIPDLParam(aWriter, aActor, mValues.Get(idx)); + } + }); +} + +template +bool Transaction::Read(IPC::MessageReader* aReader, + mozilla::ipc::IProtocol* aActor) { + // Read in which field indices were sent by the remote, followed by the fields + // identified by those indices. + typename IndexSet::serializedType modified = + typename IndexSet::serializedType{}; + if (!ReadIPDLParam(aReader, aActor, &modified)) { + return false; + } + mModified.deserialize(modified); + + bool ok = true; + EachIndex([&](auto idx) { + if (ok && mModified.contains(idx)) { + ok = ReadIPDLParam(aReader, aActor, &mValues.Get(idx)); + } + }); + return ok; +} + +template +void FieldValues::Write(IPC::MessageWriter* aWriter, + mozilla::ipc::IProtocol* aActor) const { + // XXX The this-> qualification is necessary to work around a bug in older gcc + // versions causing an ICE. + EachIndex([&](auto idx) { WriteIPDLParam(aWriter, aActor, this->Get(idx)); }); +} + +template +bool FieldValues::Read(IPC::MessageReader* aReader, + mozilla::ipc::IProtocol* aActor) { + bool ok = true; + EachIndex([&](auto idx) { + if (ok) { + // XXX The this-> qualification is necessary to work around a bug in older + // gcc versions causing an ICE. + ok = ReadIPDLParam(aReader, aActor, &this->Get(idx)); + } + }); + return ok; +} + +} // namespace syncedcontext +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_SyncedContextInlines_h) diff --git a/docshell/base/URIFixup.sys.mjs b/docshell/base/URIFixup.sys.mjs new file mode 100644 index 0000000000..212bb0ddbf --- /dev/null +++ b/docshell/base/URIFixup.sys.mjs @@ -0,0 +1,1303 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * 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 component handles fixing up URIs, by correcting obvious typos and adding + * missing schemes. + * URI references: + * http://www.faqs.org/rfcs/rfc1738.html + * http://www.faqs.org/rfcs/rfc2396.html + */ + +// TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be +// simplified, but the risk of regressing its behavior is high. +/* eslint complexity: ["error", 43] */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "externalProtocolService", + "@mozilla.org/uriloader/external-protocol-service;1", + "nsIExternalProtocolService" +); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "defaultProtocolHandler", + "@mozilla.org/network/protocol;1?name=default", + "nsIProtocolHandler" +); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "fileProtocolHandler", + "@mozilla.org/network/protocol;1?name=file", + "nsIFileProtocolHandler" +); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "handlerService", + "@mozilla.org/uriloader/handler-service;1", + "nsIHandlerService" +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "fixupSchemeTypos", + "browser.fixup.typo.scheme", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "dnsFirstForSingleWords", + "browser.fixup.dns_first_for_single_words", + false +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "keywordEnabled", + "keyword.enabled", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "alternateEnabled", + "browser.fixup.alternate.enabled", + false +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "alternateProtocol", + "browser.fixup.alternate.protocol", + "https" +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "dnsResolveFullyQualifiedNames", + "browser.urlbar.dnsResolveFullyQualifiedNames", + true +); + +const { + FIXUP_FLAG_NONE, + FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + FIXUP_FLAGS_MAKE_ALTERNATE_URI, + FIXUP_FLAG_PRIVATE_CONTEXT, + FIXUP_FLAG_FIX_SCHEME_TYPOS, + FIXUP_FLAG_FORCE_ALTERNATE_URI, +} = Ci.nsIURIFixup; + +const COMMON_PROTOCOLS = ["http", "https", "file"]; + +// Regex used to identify user:password tokens in url strings. +// This is not a strict valid characters check, because we try to fixup this +// part of the url too. +XPCOMUtils.defineLazyGetter( + lazy, + "userPasswordRegex", + () => /^([a-z+.-]+:\/{0,3})*([^\/@]+@).+/i +); + +// Regex used to identify the string that starts with port expression. +XPCOMUtils.defineLazyGetter(lazy, "portRegex", () => /^:\d{1,5}([?#/]|$)/); + +// Regex used to identify numbers. +XPCOMUtils.defineLazyGetter(lazy, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/); + +// Regex used to identify tab separated content (having at least 2 tabs). +XPCOMUtils.defineLazyGetter(lazy, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/); + +// Regex used to test if a string with a protocol might instead be a url +// without a protocol but with a port: +// +// : or +// :/ +// +// Where is a string of alphanumeric characters and dashes +// separated by dots. +// and is a 5 or less digits. This actually breaks the rfc2396 +// definition of a scheme which allows dots in schemes. +// +// Note: +// People expecting this to work with +// :@:/ will be disappointed! +// +// Note: Parser could be a lot tighter, tossing out silly hostnames +// such as those containing consecutive dots and so on. +XPCOMUtils.defineLazyGetter( + lazy, + "possiblyHostPortRegex", + () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i +); + +// Regex used to strip newlines. +XPCOMUtils.defineLazyGetter(lazy, "newLinesRegex", () => /[\r\n]/g); + +// Regex used to match a possible protocol. +// This resembles the logic in Services.io.extractScheme, thus \t is admitted +// and stripped later. We don't use Services.io.extractScheme because of +// performance bottleneck caused by crossing XPConnect. +XPCOMUtils.defineLazyGetter( + lazy, + "possibleProtocolRegex", + () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i +); + +// Regex used to match IPs. Note that these are not made to validate IPs, but +// just to detect strings that look like an IP. They also skip protocol. +// For IPv4 this also accepts a shorthand format with just 2 dots. +XPCOMUtils.defineLazyGetter( + lazy, + "IPv4LikeRegex", + () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i +); +XPCOMUtils.defineLazyGetter( + lazy, + "IPv6LikeRegex", + () => + /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i +); + +// Cache of known domains. +XPCOMUtils.defineLazyGetter(lazy, "knownDomains", () => { + const branch = "browser.fixup.domainwhitelist."; + let domains = new Set( + Services.prefs + .getChildList(branch) + .filter(p => Services.prefs.getBoolPref(p, false)) + .map(p => p.substring(branch.length)) + ); + // Hold onto the observer to avoid it being GC-ed. + domains._observer = { + observe(subject, topic, data) { + let domain = data.substring(branch.length); + if (Services.prefs.getBoolPref(data, false)) { + domains.add(domain); + } else { + domains.delete(domain); + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + }; + Services.prefs.addObserver(branch, domains._observer, true); + return domains; +}); + +// Cache of known suffixes. +// This works differently from the known domains, because when we examine a +// domain we can't tell how many dot-separated parts constitute the suffix. +// We create a Map keyed by the last dotted part, containing a Set of +// all the suffixes ending with that part: +// "two" => ["two"] +// "three" => ["some.three", "three"] +// When searching we can restrict the linear scan based on the last part. +// The ideal structure for this would be a Directed Acyclic Word Graph, but +// since we expect this list to be small it's not worth the complication. +XPCOMUtils.defineLazyGetter(lazy, "knownSuffixes", () => { + const branch = "browser.fixup.domainsuffixwhitelist."; + let suffixes = new Map(); + let prefs = Services.prefs + .getChildList(branch) + .filter(p => Services.prefs.getBoolPref(p, false)); + for (let pref of prefs) { + let suffix = pref.substring(branch.length); + let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); + if (lastPart) { + let entries = suffixes.get(lastPart); + if (!entries) { + entries = new Set(); + suffixes.set(lastPart, entries); + } + entries.add(suffix); + } + } + // Hold onto the observer to avoid it being GC-ed. + suffixes._observer = { + observe(subject, topic, data) { + let suffix = data.substring(branch.length); + let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); + let entries = suffixes.get(lastPart); + if (Services.prefs.getBoolPref(data, false)) { + // Add the suffix. + if (!entries) { + entries = new Set(); + suffixes.set(lastPart, entries); + } + entries.add(suffix); + } else if (entries) { + // Remove the suffix. + entries.delete(suffix); + if (!entries.size) { + suffixes.delete(lastPart); + } + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + }; + Services.prefs.addObserver(branch, suffixes._observer, true); + return suffixes; +}); + +export function URIFixup() { + // There are cases that nsIExternalProtocolService.externalProtocolHandlerExists() does + // not work well and returns always true due to flatpak. In this case, in order to + // fallback to nsIHandlerService.exits(), we test whether can trust + // nsIExternalProtocolService here. + this._trustExternalProtocolService = + !lazy.externalProtocolService.externalProtocolHandlerExists( + `__dummy${Date.now()}__` + ); +} + +URIFixup.prototype = { + get FIXUP_FLAG_NONE() { + return FIXUP_FLAG_NONE; + }, + get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() { + return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + }, + get FIXUP_FLAGS_MAKE_ALTERNATE_URI() { + return FIXUP_FLAGS_MAKE_ALTERNATE_URI; + }, + get FIXUP_FLAG_FORCE_ALTERNATE_URI() { + return FIXUP_FLAG_FORCE_ALTERNATE_URI; + }, + get FIXUP_FLAG_PRIVATE_CONTEXT() { + return FIXUP_FLAG_PRIVATE_CONTEXT; + }, + get FIXUP_FLAG_FIX_SCHEME_TYPOS() { + return FIXUP_FLAG_FIX_SCHEME_TYPOS; + }, + + getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) { + let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT; + + // Eliminate embedded newlines, which single-line text fields now allow, + // and cleanup the empty spaces and tabs that might be on each end. + uriString = uriString.trim().replace(lazy.newLinesRegex, ""); + + if (!uriString) { + throw new Components.Exception( + "Should pass a non-null uri", + Cr.NS_ERROR_FAILURE + ); + } + + let info = new URIFixupInfo(uriString); + + const { scheme, fixedSchemeUriString, fixupChangedProtocol } = + extractScheme(uriString, fixupFlags); + uriString = fixedSchemeUriString; + info.fixupChangedProtocol = fixupChangedProtocol; + + if (scheme == "view-source") { + let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags); + info.preferredURI = info.fixedURI = preferredURI; + info.postData = postData; + return info; + } + + if (scheme.length < 2) { + // Check if it is a file path. We skip most schemes because the only case + // where a file path may look like having a scheme is "X:" on Windows. + let fileURI = fileURIFixup(uriString); + if (fileURI) { + info.preferredURI = info.fixedURI = fileURI; + info.fixupChangedProtocol = true; + return info; + } + } + + const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme); + + let canHandleProtocol = + scheme && + (isCommonProtocol || + Services.io.getProtocolHandler(scheme) != lazy.defaultProtocolHandler || + this._isKnownExternalProtocol(scheme)); + + if ( + canHandleProtocol || + // If it's an unknown handler and the given URL looks like host:port or + // has a user:password we can't pass it to the external protocol handler. + // We'll instead try fixing it with http later. + (!lazy.possiblyHostPortRegex.test(uriString) && + !lazy.userPasswordRegex.test(uriString)) + ) { + // Just try to create an URL out of it. + try { + info.fixedURI = Services.io.newURI(uriString); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { + throw ex; + } + } + } + + // We're dealing with a theoretically valid URI but we have no idea how to + // load it. (e.g. "christmas:humbug") + // It's more likely the user wants to search, and so we chuck this over to + // their preferred search provider. + // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP + // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS. + if ( + info.fixedURI && + lazy.keywordEnabled && + fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && + scheme && + !canHandleProtocol + ) { + tryKeywordFixupForURIInfo(uriString, info, isPrivateContext); + } + + if (info.fixedURI) { + if (!info.preferredURI) { + maybeSetAlternateFixedURI(info, fixupFlags); + info.preferredURI = info.fixedURI; + } + fixupConsecutiveDotsHost(info); + return info; + } + + // Fix up protocol string before calling KeywordURIFixup, because + // it cares about the hostname of such URIs. + // Prune duff protocol schemes: + // ://totallybroken.url.com + // //shorthand.url.com + let inputHadDuffProtocol = + uriString.startsWith("://") || uriString.startsWith("//"); + if (inputHadDuffProtocol) { + uriString = uriString.replace(/^:?\/\//, ""); + } + + // Avoid fixing up content that looks like tab-separated values. + // Assume that 1 tab is accidental, but more than 1 implies this is + // supposed to be tab-separated content. + if (!isCommonProtocol && lazy.maxOneTabRegex.test(uriString)) { + let uriWithProtocol = fixupURIProtocol(uriString); + if (uriWithProtocol) { + info.fixedURI = uriWithProtocol; + info.fixupChangedProtocol = true; + maybeSetAlternateFixedURI(info, fixupFlags); + info.preferredURI = info.fixedURI; + // Check if it's a forced visit. The user can enforce a visit by + // appending a slash, but the string must be in a valid uri format. + if (uriString.endsWith("/")) { + fixupConsecutiveDotsHost(info); + return info; + } + } + } + + // Handle "www." as a URI. + const asciiHost = info.fixedURI?.asciiHost; + if ( + asciiHost?.length > 4 && + asciiHost?.startsWith("www.") && + asciiHost?.lastIndexOf(".") == 3 + ) { + return info; + } + + // Memoize the public suffix check, since it may be expensive and should + // only run once when necessary. + let suffixInfo; + function checkSuffix(info) { + if (!suffixInfo) { + suffixInfo = checkAndFixPublicSuffix(info); + } + return suffixInfo; + } + + // See if it is a keyword and whether a keyword must be fixed up. + if ( + lazy.keywordEnabled && + fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP && + !inputHadDuffProtocol && + !checkSuffix(info).suffix && + keywordURIFixup(uriString, info, isPrivateContext) + ) { + fixupConsecutiveDotsHost(info); + return info; + } + + if ( + info.fixedURI && + (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix) + ) { + fixupConsecutiveDotsHost(info); + return info; + } + + // If we still haven't been able to construct a valid URI, try to force a + // keyword match. + if (lazy.keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) { + tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext); + } + + if (!info.preferredURI) { + // We couldn't salvage anything. + throw new Components.Exception( + "Couldn't build a valid uri", + Cr.NS_ERROR_MALFORMED_URI + ); + } + + fixupConsecutiveDotsHost(info); + return info; + }, + + webNavigationFlagsToFixupFlags(href, navigationFlags) { + try { + Services.io.newURI(href); + // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris. + navigationFlags &= + ~Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } catch (ex) {} + + let fixupFlags = FIXUP_FLAG_NONE; + if ( + navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP + ) { + fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + } + if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) { + fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS; + } + return fixupFlags; + }, + + keywordToURI(keyword, isPrivateContext) { + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { + // There's no search service in the content process, thus all the calls + // from it that care about keywords conversion should go through the + // parent process. + throw new Components.Exception( + "Can't invoke URIFixup in the content process", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + let info = new URIFixupInfo(keyword); + + // Strip leading "?" and leading/trailing spaces from aKeyword + if (keyword.startsWith("?")) { + keyword = keyword.substring(1); + } + keyword = keyword.trim(); + + if (!Services.search.hasSuccessfullyInitialized) { + return info; + } + + // Try falling back to the search service's default search engine + // We must use an appropriate search engine depending on the private + // context. + let engine = isPrivateContext + ? Services.search.defaultPrivateEngine + : Services.search.defaultEngine; + + // We allow default search plugins to specify alternate parameters that are + // specific to keyword searches. + let responseType = null; + if (engine.supportsResponseType("application/x-moz-keywordsearch")) { + responseType = "application/x-moz-keywordsearch"; + } + let submission = engine.getSubmission(keyword, responseType, "keyword"); + if ( + !submission || + // For security reasons (avoid redirecting to file, data, or other unsafe + // protocols) we only allow fixup to http/https search engines. + !submission.uri.scheme.startsWith("http") + ) { + throw new Components.Exception( + "Invalid search submission uri", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + let submissionPostDataStream = submission.postData; + if (submissionPostDataStream) { + info.postData = submissionPostDataStream; + } + + info.keywordProviderName = engine.name; + info.keywordAsSent = keyword; + info.preferredURI = submission.uri; + return info; + }, + + forceHttpFixup(uriString) { + if (!uriString) { + throw new Components.Exception( + "Should pass a non-null uri", + Cr.NS_ERROR_FAILURE + ); + } + + let info = new URIFixupInfo(uriString); + let { scheme, fixedSchemeUriString, fixupChangedProtocol } = extractScheme( + uriString, + FIXUP_FLAG_FIX_SCHEME_TYPOS + ); + + if (scheme != "http" && scheme != "https") { + throw new Components.Exception( + "Scheme should be either http or https", + Cr.NS_ERROR_FAILURE + ); + } + + info.fixupChangedProtocol = fixupChangedProtocol; + info.fixedURI = Services.io.newURI(fixedSchemeUriString); + + let host = info.fixedURI.host; + if (host != "http" && host != "https" && host != "localhost") { + let modifiedHostname = maybeAddPrefixAndSuffix(host); + updateHostAndScheme(info, modifiedHostname); + info.preferredURI = info.fixedURI; + } + + return info; + }, + + checkHost(uri, listener, originAttributes) { + let { displayHost, asciiHost } = uri; + if (!displayHost) { + throw new Components.Exception( + "URI must have displayHost", + Cr.NS_ERROR_FAILURE + ); + } + if (!asciiHost) { + throw new Components.Exception( + "URI must have asciiHost", + Cr.NS_ERROR_FAILURE + ); + } + + let isIPv4Address = host => { + let parts = host.split("."); + if (parts.length != 4) { + return false; + } + return parts.every(part => { + let n = parseInt(part, 10); + return n >= 0 && n <= 255; + }); + }; + + // Avoid showing fixup information if we're suggesting an IP. Note that + // decimal representations of IPs are normalized to a 'regular' + // dot-separated IP address by network code, but that only happens for + // numbers that don't overflow. Longer numbers do not get normalized, + // but still work to access IP addresses. So for instance, + // 1097347366913 (ff7f000001) gets resolved by using the final bytes, + // making it the same as 7f000001, which is 127.0.0.1 aka localhost. + // While 2130706433 would get normalized by network, 1097347366913 + // does not, and we have to deal with both cases here: + if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost)) { + return; + } + + // For dotless hostnames, we want to ensure this ends with a '.' but don't + // want the . showing up in the UI if we end up notifying the user, so we + // use a separate variable. + let lookupName = displayHost; + if (lazy.dnsResolveFullyQualifiedNames && !lookupName.includes(".")) { + lookupName += "."; + } + + Services.obs.notifyObservers(null, "uri-fixup-check-dns"); + Services.dns.asyncResolve( + lookupName, + Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, + 0, + null, + listener, + Services.tm.mainThread, + originAttributes + ); + }, + + isDomainKnown, + + _isKnownExternalProtocol(scheme) { + if (this._trustExternalProtocolService) { + return lazy.externalProtocolService.externalProtocolHandlerExists(scheme); + } + + try { + // nsIExternalProtocolService.getProtocolHandlerInfo() on Android throws + // error due to not implemented. + return lazy.handlerService.exists( + lazy.externalProtocolService.getProtocolHandlerInfo(scheme) + ); + } catch (e) { + return false; + } + }, + + classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"), + QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]), +}; + +export function URIFixupInfo(originalInput = "") { + this._originalInput = originalInput; +} + +URIFixupInfo.prototype = { + set consumer(consumer) { + this._consumer = consumer || null; + }, + get consumer() { + return this._consumer || null; + }, + + set preferredURI(uri) { + this._preferredURI = uri; + }, + get preferredURI() { + return this._preferredURI || null; + }, + + set fixedURI(uri) { + this._fixedURI = uri; + }, + get fixedURI() { + return this._fixedURI || null; + }, + + set keywordProviderName(name) { + this._keywordProviderName = name; + }, + get keywordProviderName() { + return this._keywordProviderName || ""; + }, + + set keywordAsSent(keyword) { + this._keywordAsSent = keyword; + }, + get keywordAsSent() { + return this._keywordAsSent || ""; + }, + + set fixupChangedProtocol(changed) { + this._fixupChangedProtocol = changed; + }, + get fixupChangedProtocol() { + return !!this._fixupChangedProtocol; + }, + + set fixupCreatedAlternateURI(changed) { + this._fixupCreatedAlternateURI = changed; + }, + get fixupCreatedAlternateURI() { + return !!this._fixupCreatedAlternateURI; + }, + + set originalInput(input) { + this._originalInput = input; + }, + get originalInput() { + return this._originalInput || ""; + }, + + set postData(postData) { + this._postData = postData; + }, + get postData() { + return this._postData || null; + }, + + classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"), + QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]), +}; + +// Helpers + +/** + * Implementation of isDomainKnown, so we don't have to go through the + * service. + * @param {string} asciiHost + * @returns {boolean} whether the domain is known + */ +function isDomainKnown(asciiHost) { + if (lazy.dnsFirstForSingleWords) { + return true; + } + // Check if this domain is known as an actual + // domain (which will prevent a keyword query) + // Note that any processing of the host here should stay in sync with + // code in the front-end(s) that set the pref. + let lastDotIndex = asciiHost.lastIndexOf("."); + if (lastDotIndex == asciiHost.length - 1) { + asciiHost = asciiHost.substring(0, asciiHost.length - 1); + lastDotIndex = asciiHost.lastIndexOf("."); + } + if (lazy.knownDomains.has(asciiHost.toLowerCase())) { + return true; + } + // If there's no dot or only a leading dot we are done, otherwise we'll check + // against the known suffixes. + if (lastDotIndex <= 0) { + return false; + } + // Don't use getPublicSuffix here, since the suffix is not in the PSL, + // thus it couldn't tell if the suffix is made up of one or multiple + // dot-separated parts. + let lastPart = asciiHost.substr(lastDotIndex + 1); + let suffixes = lazy.knownSuffixes.get(lastPart); + if (suffixes) { + return Array.from(suffixes).some(s => asciiHost.endsWith(s)); + } + return false; +} + +/** + * Checks the suffix of info.fixedURI against the Public Suffix List. + * If the suffix is unknown due to a typo this will try to fix it up. + * @param {URIFixupInfo} info about the uri to check. + * @note this may modify the public suffix of info.fixedURI. + * @returns {object} result The lookup result. + * @returns {string} result.suffix The public suffix if one can be identified. + * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the + * Public Suffix List and it's not in knownSuffixes. False in the other cases. + */ +function checkAndFixPublicSuffix(info) { + let uri = info.fixedURI; + let asciiHost = uri?.asciiHost; + if ( + !asciiHost || + !asciiHost.includes(".") || + asciiHost.endsWith(".") || + isDomainKnown(asciiHost) + ) { + return { suffix: "", hasUnknownSuffix: false }; + } + + // Quick bailouts for most common cases, according to Alexa Top 1 million. + if ( + /^\w/.test(asciiHost) && + (asciiHost.endsWith(".com") || + asciiHost.endsWith(".net") || + asciiHost.endsWith(".org") || + asciiHost.endsWith(".ru") || + asciiHost.endsWith(".de")) + ) { + return { + suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1), + hasUnknownSuffix: false, + }; + } + try { + let suffix = Services.eTLD.getKnownPublicSuffix(uri); + if (suffix) { + return { suffix, hasUnknownSuffix: false }; + } + } catch (ex) { + return { suffix: "", hasUnknownSuffix: false }; + } + // Suffix is unknown, try to fix most common 3 chars TLDs typos. + // .com is the most commonly mistyped tld, so it has more cases. + let suffix = Services.eTLD.getPublicSuffix(uri); + if (!suffix || lazy.numberRegex.test(suffix)) { + return { suffix: "", hasUnknownSuffix: false }; + } + for (let [typo, fixed] of [ + ["ocm", "com"], + ["con", "com"], + ["cmo", "com"], + ["xom", "com"], + ["vom", "com"], + ["cpm", "com"], + ["com'", "com"], + ["ent", "net"], + ["ner", "net"], + ["nte", "net"], + ["met", "net"], + ["rog", "org"], + ["ogr", "org"], + ["prg", "org"], + ["orh", "org"], + ]) { + if (suffix == typo) { + let host = uri.host.substring(0, uri.host.length - typo.length) + fixed; + let updatePreferredURI = info.preferredURI == info.fixedURI; + info.fixedURI = uri.mutate().setHost(host).finalize(); + if (updatePreferredURI) { + info.preferredURI = info.fixedURI; + } + return { suffix: fixed, hasUnknownSuffix: false }; + } + } + return { suffix: "", hasUnknownSuffix: true }; +} + +function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) { + try { + let keywordInfo = Services.uriFixup.keywordToURI( + uriString, + isPrivateContext + ); + fixupInfo.keywordProviderName = keywordInfo.keywordProviderName; + fixupInfo.keywordAsSent = keywordInfo.keywordAsSent; + fixupInfo.preferredURI = keywordInfo.preferredURI; + return true; + } catch (ex) {} + return false; +} + +/** + * This generates an alternate fixedURI, by adding a prefix and a suffix to + * the fixedURI host, if and only if the protocol is http. It should _never_ + * modify URIs with other protocols. + * @param {URIFixupInfo} info an URIInfo object + * @param {integer} fixupFlags the fixup flags + * @returns {boolean} Whether an alternate uri was generated + */ +function maybeSetAlternateFixedURI(info, fixupFlags) { + let uri = info.fixedURI; + let canUseAlternate = + fixupFlags & FIXUP_FLAG_FORCE_ALTERNATE_URI || + (lazy.alternateEnabled && fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI); + if ( + !canUseAlternate || + // Code only works for http. Not for any other protocol including https! + !uri.schemeIs("http") || + // Security - URLs with user / password info should NOT be fixed up + uri.userPass || + // Don't fix up hosts with ports + uri.port != -1 + ) { + return false; + } + + let oldHost = uri.host; + // Don't create an alternate uri for localhost, because it would be confusing. + // Ditto for 'http' and 'https' as these are frequently the result of typos, e.g. + // 'https//foo' (note missing : ). + if (oldHost == "localhost" || oldHost == "http" || oldHost == "https") { + return false; + } + + // Get the prefix and suffix to stick onto the new hostname. By default these + // are www. & .com but they could be any other value, e.g. www. & .org + let newHost = maybeAddPrefixAndSuffix(oldHost); + + if (newHost == oldHost) { + return false; + } + + return updateHostAndScheme(info, newHost); +} + +/** + * Try to fixup a file URI. + * @param {string} uriString The file URI to fix. + * @returns {nsIURI} a fixed uri or null. + * @note FileURIFixup only returns a URI if it has to add the file: protocol. + */ +function fileURIFixup(uriString) { + let attemptFixup = false; + let path = uriString; + if (AppConstants.platform == "win") { + // Check for "\"" in the url-string, just a drive (e.g. C:), + // or 'A:/...' where the "protocol" is also a single letter. + attemptFixup = + uriString.includes("\\") || + (uriString[1] == ":" && (uriString.length == 2 || uriString[2] == "/")); + if (uriString[1] == ":" && uriString[2] == "/") { + path = uriString.replace(/\//g, "\\"); + } + } else { + // UNIX: Check if it starts with "/". + attemptFixup = uriString.startsWith("/"); + } + if (attemptFixup) { + try { + // Test if this is a valid path by trying to create a local file + // object. The URL of that is returned if successful. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(path); + return Services.io.newURI( + lazy.fileProtocolHandler.getURLSpecFromActualFile(file) + ); + } catch (ex) { + // Not a file uri. + } + } + return null; +} + +/** + * Tries to fixup a string to an nsIURI by adding the default protocol. + * + * Should fix things like: + * no-scheme.com + * ftp.no-scheme.com + * ftp4.no-scheme.com + * no-scheme.com/query?foo=http://www.foo.com + * user:pass@no-scheme.com + * + * @param {string} uriString The string to fixup. + * @returns {nsIURI} an nsIURI built adding the default protocol to the string, + * or null if fixing was not possible. + */ +function fixupURIProtocol(uriString) { + let schemePos = uriString.indexOf("://"); + if (schemePos == -1 || schemePos > uriString.search(/[:\/]/)) { + uriString = "http://" + uriString; + } + try { + return Services.io.newURI(uriString); + } catch (ex) { + // We generated an invalid uri. + } + return null; +} + +/** + * Tries to fixup a string to a search url. + * @param {string} uriString the string to fixup. + * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place. + * @param {boolean} isPrivateContext Whether this happens in a private context. + * @param {nsIInputStream} postData optional POST data for the search + * @returns {boolean} Whether the keyword fixup was succesful. + */ +function keywordURIFixup(uriString, fixupInfo, isPrivateContext) { + // Here is a few examples of strings that should be searched: + // "what is mozilla" + // "what is mozilla?" + // "docshell site:mozilla.org" - has a space in the origin part + // "?site:mozilla.org - anything that begins with a question mark + // "mozilla'.org" - Things that have a quote before the first dot/colon + // "mozilla/test" - unknown host + // ".mozilla", "mozilla." - starts or ends with a dot () + // "user@nonQualifiedHost" + + // These other strings should not be searched, because they could be URIs: + // "www.blah.com" - Domain with a standard or known suffix + // "knowndomain" - known domain + // "nonQualifiedHost:8888?something" - has a port + // "user:pass@nonQualifiedHost" + // "blah.com." + + // We do keyword lookups if the input starts with a question mark. + if (uriString.startsWith("?")) { + return tryKeywordFixupForURIInfo( + fixupInfo.originalInput, + fixupInfo, + isPrivateContext + ); + } + + // Check for IPs. + const userPassword = lazy.userPasswordRegex.exec(uriString); + const ipString = userPassword + ? uriString.replace(userPassword[2], "") + : uriString; + if (lazy.IPv4LikeRegex.test(ipString) || lazy.IPv6LikeRegex.test(ipString)) { + return false; + } + + // Avoid keyword lookup if we can identify a host and it's known, or ends + // with a dot and has some path. + // Note that if dnsFirstForSingleWords is true isDomainKnown will always + // return true, so we can avoid checking dnsFirstForSingleWords after this. + let asciiHost = fixupInfo.fixedURI?.asciiHost; + if ( + asciiHost && + (isDomainKnown(asciiHost) || + (asciiHost.endsWith(".") && + asciiHost.indexOf(".") != asciiHost.length - 1)) + ) { + return false; + } + + // Avoid keyword lookup if the url seems to have password. + if (fixupInfo.fixedURI?.password) { + return false; + } + + // Even if the host is unknown, avoid keyword lookup if the string has + // uri-like characteristics, unless it looks like "user@unknownHost". + // Note we already excluded passwords at this point. + if ( + !isURILike(uriString, fixupInfo.fixedURI?.displayHost) || + (fixupInfo.fixedURI?.userPass && fixupInfo.fixedURI?.pathQueryRef === "/") + ) { + return tryKeywordFixupForURIInfo( + fixupInfo.originalInput, + fixupInfo, + isPrivateContext + ); + } + + return false; +} + +/** + * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect. + * This also tries to fixup the scheme if it was clearly mistyped. + * @param {string} uriString the string to examine + * @param {integer} fixupFlags The original fixup flags + * @returns {object} + * scheme: a typo fixed scheme or empty string if one could not be identified + * fixedSchemeUriString: uri string with a typo fixed scheme + * fixupChangedProtocol: true if the scheme is fixed up + */ +function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) { + const matches = uriString.match(lazy.possibleProtocolRegex); + const hasColon = matches?.[2] === ":"; + const hasSlash2 = matches?.[3] === "//"; + + const isFixupSchemeTypos = + lazy.fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS; + + if ( + !matches || + (!hasColon && !hasSlash2) || + (!hasColon && !isFixupSchemeTypos) + ) { + return { + scheme: "", + fixedSchemeUriString: uriString, + fixupChangedProtocol: false, + }; + } + + let scheme = matches[1].replace("\t", "").toLowerCase(); + let fixedSchemeUriString = uriString; + + if (isFixupSchemeTypos && hasSlash2) { + // Fix up typos for string that user would have intented as protocol. + const afterProtocol = uriString.substring(matches[0].length); + fixedSchemeUriString = `${scheme}://${afterProtocol}`; + } + + let fixupChangedProtocol = false; + + if (isFixupSchemeTypos) { + // Fix up common scheme typos. + // TODO: Use levenshtein distance here? + fixupChangedProtocol = [ + ["ttp", "http"], + ["htp", "http"], + ["ttps", "https"], + ["tps", "https"], + ["ps", "https"], + ["htps", "https"], + ["ile", "file"], + ["le", "file"], + ].some(([typo, fixed]) => { + if (scheme === typo) { + scheme = fixed; + fixedSchemeUriString = + scheme + fixedSchemeUriString.substring(typo.length); + return true; + } + return false; + }); + } + + return { + scheme, + fixedSchemeUriString, + fixupChangedProtocol, + }; +} + +/** + * View-source is a pseudo scheme. We're interested in fixing up the stuff + * after it. The easiest way to do that is to call this method again with + * the "view-source:" lopped off and then prepend it again afterwards. + * @param {string} uriString The original string to fixup + * @param {integer} fixupFlags The original fixup flags + * @param {nsIInputStream} postData Optional POST data for the search + * @returns {object} {preferredURI, postData} The fixed URI and relative postData + * @throws if it's not possible to fixup the url + */ +function fixupViewSource(uriString, fixupFlags) { + // We disable keyword lookup and alternate URIs so that small typos don't + // cause us to look at very different domains. + let newFixupFlags = + fixupFlags & + ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP & + ~FIXUP_FLAGS_MAKE_ALTERNATE_URI; + + let innerURIString = uriString.substring(12).trim(); + + // Prevent recursion. + const { scheme: innerScheme } = extractScheme(innerURIString); + if (innerScheme == "view-source") { + throw new Components.Exception( + "Prevent view-source recursion", + Cr.NS_ERROR_FAILURE + ); + } + + let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags); + if (!info.preferredURI) { + throw new Components.Exception( + "Couldn't build a valid uri", + Cr.NS_ERROR_MALFORMED_URI + ); + } + return { + preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec), + postData: info.postData, + }; +} + +/** + * Fixup the host of fixedURI if it contains consecutive dots. + * @param {URIFixupInfo} info an URIInfo object + */ +function fixupConsecutiveDotsHost(fixupInfo) { + const uri = fixupInfo.fixedURI; + + try { + if (!uri?.host.includes("..")) { + return; + } + } catch (e) { + return; + } + + try { + const isPreferredEqualsToFixed = fixupInfo.preferredURI?.equals(uri); + + fixupInfo.fixedURI = uri + .mutate() + .setHost(uri.host.replace(/\.+/g, ".")) + .finalize(); + + if (isPreferredEqualsToFixed) { + fixupInfo.preferredURI = fixupInfo.fixedURI; + } + } catch (e) { + if (e.result !== Cr.NS_ERROR_MALFORMED_URI) { + throw e; + } + } +} + +/** + * Return whether or not given string is uri like. + * This function returns true like following strings. + * - ":8080" + * - "localhost:8080" (if given host is "localhost") + * - "/foo?bar" + * - "/foo#bar" + * @param {string} uriString. + * @param {string} host. + * @param {boolean} true if uri like. + */ +function isURILike(uriString, host) { + const indexOfSlash = uriString.indexOf("/"); + if ( + indexOfSlash >= 0 && + (indexOfSlash < uriString.indexOf("?", indexOfSlash) || + indexOfSlash < uriString.indexOf("#", indexOfSlash)) + ) { + return true; + } + + if (uriString.startsWith(host)) { + uriString = uriString.substring(host.length); + } + + return lazy.portRegex.test(uriString); +} + +/** + * Add prefix and suffix to a hostname if both are missing. + * + * If the host does not start with the prefix, add the prefix to + * the hostname. + * + * By default the prefix and suffix are www. and .com but they could + * be any value e.g. www. and .org as they use the preferences + * "browser.fixup.alternate.prefix" and "browser.fixup.alternative.suffix" + * + * If no changes were made, it returns an empty string. + * + * @param {string} oldHost. + * @return {String} Fixed up hostname or an empty string. + */ +function maybeAddPrefixAndSuffix(oldHost) { + let prefix = Services.prefs.getCharPref( + "browser.fixup.alternate.prefix", + "www." + ); + let suffix = Services.prefs.getCharPref( + "browser.fixup.alternate.suffix", + ".com" + ); + let newHost = ""; + let numDots = (oldHost.match(/\./g) || []).length; + if (numDots == 0) { + newHost = prefix + oldHost + suffix; + } else if (numDots == 1) { + if (prefix && oldHost == prefix) { + newHost = oldHost + suffix; + } else if (suffix && !oldHost.startsWith(prefix)) { + newHost = prefix + oldHost; + } + } + return newHost ? newHost : oldHost; +} + +/** + * Given an instance of URIFixupInfo, update its fixedURI. + * + * First, change the protocol to the one stored in + * "browser.fixup.alternate.protocol". + * + * Then, try to update fixedURI's host to newHost. + * + * @param {URIFixupInfo} info. + * @param {string} newHost. + * @return {boolean} + * True, if info was updated without any errors. + * False, if NS_ERROR_MALFORMED_URI error. + * @throws If a non-NS_ERROR_MALFORMED_URI error occurs. + */ +function updateHostAndScheme(info, newHost) { + let oldHost = info.fixedURI.host; + let oldScheme = info.fixedURI.scheme; + try { + info.fixedURI = info.fixedURI + .mutate() + .setScheme(lazy.alternateProtocol) + .setHost(newHost) + .finalize(); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { + throw ex; + } + return false; + } + if (oldScheme != info.fixedURI.scheme) { + info.fixupChangedProtocol = true; + } + if (oldHost != info.fixedURI.host) { + info.fixupCreatedAlternateURI = true; + } + return true; +} diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp new file mode 100644 index 0000000000..02b4e7d4a8 --- /dev/null +++ b/docshell/base/WindowContext.cpp @@ -0,0 +1,662 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowGlobalActorsBinding.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/SyncedContextInlines.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/UserActivationIPCUtils.h" +#include "mozilla/PermissionDelegateIPCUtils.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsGlobalWindowInner.h" +#include "nsIScriptError.h" +#include "nsIXULRuntime.h" +#include "nsRefPtrHashtable.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace dom { + +// Explicit specialization of the `Transaction` type. Required by the `extern +// template class` declaration in the header. +template class syncedcontext::Transaction; + +static LazyLogModule gWindowContextLog("WindowContext"); +static LazyLogModule gWindowContextSyncLog("WindowContextSync"); + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +using WindowContextByIdMap = nsTHashMap; +static StaticAutoPtr gWindowContexts; + +/* static */ +LogModule* WindowContext::GetLog() { return gWindowContextLog; } + +/* static */ +LogModule* WindowContext::GetSyncLog() { return gWindowContextSyncLog; } + +/* static */ +already_AddRefed WindowContext::GetById( + uint64_t aInnerWindowId) { + if (!gWindowContexts) { + return nullptr; + } + return do_AddRef(gWindowContexts->Get(aInnerWindowId)); +} + +BrowsingContextGroup* WindowContext::Group() const { + return mBrowsingContext->Group(); +} + +WindowGlobalParent* WindowContext::Canonical() { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return static_cast(this); +} + +bool WindowContext::IsCurrent() const { + return mBrowsingContext->mCurrentWindowContext == this; +} + +bool WindowContext::IsInBFCache() { + if (mozilla::SessionHistoryInParent()) { + return mBrowsingContext->IsInBFCache(); + } + return TopWindowContext()->GetWindowStateSaved(); +} + +nsGlobalWindowInner* WindowContext::GetInnerWindow() const { + return mWindowGlobalChild ? mWindowGlobalChild->GetWindowGlobal() : nullptr; +} + +Document* WindowContext::GetDocument() const { + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetDocument() : nullptr; +} + +Document* WindowContext::GetExtantDoc() const { + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetExtantDoc() : nullptr; +} + +WindowGlobalChild* WindowContext::GetWindowGlobalChild() const { + return mWindowGlobalChild; +} + +WindowContext* WindowContext::GetParentWindowContext() { + return mBrowsingContext->GetParentWindowContext(); +} + +WindowContext* WindowContext::TopWindowContext() { + WindowContext* current = this; + while (current->GetParentWindowContext()) { + current = current->GetParentWindowContext(); + } + return current; +} + +bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); } + +bool WindowContext::SameOriginWithTop() const { + return mBrowsingContext->SameOriginWithTop(); +} + +nsIGlobalObject* WindowContext::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +void WindowContext::AppendChildBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), + "Mismatched groups?"); + MOZ_DIAGNOSTIC_ASSERT(!mChildren.Contains(aBrowsingContext)); + + mChildren.AppendElement(aBrowsingContext); + if (!nsContentUtils::ShouldHideObjectOrEmbedImageDocument() || + !aBrowsingContext->IsEmbedderTypeObjectOrEmbed()) { + mNonSyntheticChildren.AppendElement(aBrowsingContext); + } + + // If we're the current WindowContext in our BrowsingContext, make sure to + // clear any cached `children` value. + if (IsCurrent()) { + BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); + } +} + +void WindowContext::RemoveChildBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), + "Mismatched groups?"); + + mChildren.RemoveElement(aBrowsingContext); + mNonSyntheticChildren.RemoveElement(aBrowsingContext); + + // If we're the current WindowContext in our BrowsingContext, make sure to + // clear any cached `children` value. + if (IsCurrent()) { + BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); + } +} + +void WindowContext::UpdateChildSynthetic(BrowsingContext* aBrowsingContext, + bool aIsSynthetic) { + MOZ_ASSERT(nsContentUtils::ShouldHideObjectOrEmbedImageDocument()); + if (aIsSynthetic) { + mNonSyntheticChildren.RemoveElement(aBrowsingContext); + } else { + // The same BrowsingContext will be reused for error pages, so it can be in + // the list already. + if (!mNonSyntheticChildren.Contains(aBrowsingContext)) { + mNonSyntheticChildren.AppendElement(aBrowsingContext); + } + } +} + +void WindowContext::SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + Unused << aParent->SendCommitWindowContextTransaction(this, aTxn, aEpoch); +} + +void WindowContext::SendCommitTransaction(ContentChild* aChild, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch); +} + +bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) { + if (IsInProcess()) { + return true; + } + + if (XRE_IsParentProcess() && aSource) { + return Canonical()->GetContentParent() == aSource; + } + + return false; +} + +bool WindowContext::CanSet(FieldIndex, const bool& aIsSecure, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aAllowMixedContent, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aHasBeforeUnload, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const Maybe& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aValue, ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& IsThirdPartyWindow, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aIsThirdPartyTrackingResourceWindow, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aShouldResistFingerprinting, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aIsSecureContext, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aIsOriginalFrameSource, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return IsTop(); +} + +bool WindowContext::CanSet(FieldIndex, + const uint32_t& aValue, ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const uint32_t& aValue, ContentParent* aSource) { + return IsTop() && CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const Maybe& aValue, + ContentParent* aSource) { + return IsTop(); +} + +bool WindowContext::CanSet(FieldIndex, const uint32_t&, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet( + FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet( + FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, bool aValue, + ContentParent* aSource) { + return (XRE_IsParentProcess() && !aSource) || + CheckOnlyOwningProcessCanSet(aSource); +} + +void WindowContext::DidSet(FieldIndex, bool aOldValue) { + RecomputeCanExecuteScripts(); +} + +bool WindowContext::CanSet(FieldIndex, bool, + ContentParent*) { + return XRE_IsParentProcess() && IsTop(); +} + +void WindowContext::RecomputeCanExecuteScripts(bool aApplyChanges) { + const bool old = mCanExecuteScripts; + if (!AllowJavascript()) { + // Scripting has been explicitly disabled on our WindowContext. + mCanExecuteScripts = false; + } else { + // Otherwise, inherit. + mCanExecuteScripts = mBrowsingContext->CanExecuteScripts(); + } + + if (aApplyChanges && old != mCanExecuteScripts) { + // Inform our active DOM window. + if (nsGlobalWindowInner* window = GetInnerWindow()) { + // Only update scriptability if the window is current. Windows will have + // scriptability disabled when entering the bfcache and updated when + // coming out. + if (window->IsCurrentInnerWindow()) { + auto& scriptability = + xpc::Scriptability::Get(window->GetGlobalJSObject()); + scriptability.SetWindowAllowsScript(mCanExecuteScripts); + } + } + + for (const RefPtr& child : Children()) { + child->RecomputeCanExecuteScripts(); + } + } +} + +void WindowContext::DidSet(FieldIndex, + bool aOldValue) { + MOZ_ASSERT( + TopWindowContext() == this, + "SHEntryHasUserInteraction can only be set on the top window context"); + // This field is set when the child notifies us of new user interaction, so we + // also set the currently active shentry in the parent as having interaction. + if (XRE_IsParentProcess() && mBrowsingContext) { + SessionHistoryEntry* activeEntry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (activeEntry && GetSHEntryHasUserInteraction()) { + activeEntry->SetHasUserInteraction(true); + } + } +} + +void WindowContext::DidSet(FieldIndex) { + MOZ_ASSERT_IF(!IsInProcess(), mUserGestureStart.IsNull()); + USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8 + " for %s browsing context 0x%08" PRIx64, + static_cast(GetUserActivationState()), + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + if (IsInProcess()) { + USER_ACTIVATION_LOG( + "Set user gesture start time for %s browsing context 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + if (GetUserActivationState() == UserActivation::State::FullActivated) { + mUserGestureStart = TimeStamp::Now(); + } else if (GetUserActivationState() == UserActivation::State::None) { + mUserGestureStart = TimeStamp(); + } + } +} + +void WindowContext::DidSet(FieldIndex, + bool aOldValue) { + if (!aOldValue && GetHasReportedShadowDOMUsage() && IsInProcess()) { + MOZ_ASSERT(TopWindowContext() == this); + if (mBrowsingContext) { + Document* topLevelDoc = mBrowsingContext->GetDocument(); + if (topLevelDoc) { + nsAutoString uri; + Unused << topLevelDoc->GetDocumentURI(uri); + if (!uri.IsEmpty()) { + nsAutoString msg = u"Shadow DOM used in ["_ns + uri + + u"] or in some of its subdocuments."_ns; + nsContentUtils::ReportToConsoleNonLocalized( + msg, nsIScriptError::infoFlag, "DOM"_ns, topLevelDoc); + } + } + } + } +} + +bool WindowContext::CanSet(FieldIndex, bool aValue, + ContentParent* aSource) { + return !mozilla::SessionHistoryInParent() && IsTop() && + CheckOnlyOwningProcessCanSet(aSource); +} + +void WindowContext::CreateFromIPC(IPCInitializer&& aInit) { + MOZ_RELEASE_ASSERT(XRE_IsContentProcess(), + "Should be a WindowGlobalParent in the parent"); + + RefPtr bc = BrowsingContext::Get(aInit.mBrowsingContextId); + MOZ_RELEASE_ASSERT(bc); + + if (bc->IsDiscarded()) { + // If we have already closed our browsing context, the + // WindowGlobalChild actor is bound to be destroyed soon and it's + // safe to ignore creating the WindowContext. + return; + } + + RefPtr context = new WindowContext( + bc, aInit.mInnerWindowId, aInit.mOuterWindowId, std::move(aInit.mFields)); + context->Init(); +} + +void WindowContext::Init() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, + mBrowsingContext->Id())); + + // Register the WindowContext in the `WindowContextByIdMap`. + if (!gWindowContexts) { + gWindowContexts = new WindowContextByIdMap(); + ClearOnShutdown(&gWindowContexts); + } + auto& entry = gWindowContexts->LookupOrInsert(mInnerWindowId); + MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowContext for ID!"); + entry = this; + + // Register this to the browsing context. + mBrowsingContext->RegisterWindowContext(this); + Group()->Register(this); +} + +void WindowContext::Discard() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Discarding 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, + mBrowsingContext->Id())); + if (mIsDiscarded) { + return; + } + + mIsDiscarded = true; + if (gWindowContexts) { + gWindowContexts->Remove(InnerWindowId()); + } + mBrowsingContext->UnregisterWindowContext(this); + Group()->Unregister(this); +} + +void WindowContext::AddSecurityState(uint32_t aStateFlags) { + MOZ_ASSERT(TopWindowContext() == this); + MOZ_ASSERT((aStateFlags & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED)) == + aStateFlags, + "Invalid flags specified!"); + + if (XRE_IsParentProcess()) { + Canonical()->AddSecurityState(aStateFlags); + } else { + ContentChild* child = ContentChild::GetSingleton(); + child->SendAddSecurityState(this, aStateFlags); + } +} + +void WindowContext::NotifyUserGestureActivation() { + Unused << SetUserActivationState(UserActivation::State::FullActivated); +} + +void WindowContext::NotifyResetUserGestureActivation() { + Unused << SetUserActivationState(UserActivation::State::None); +} + +bool WindowContext::HasBeenUserGestureActivated() { + return GetUserActivationState() != UserActivation::State::None; +} + +const TimeStamp& WindowContext::GetUserGestureStart() const { + MOZ_ASSERT(IsInProcess()); + return mUserGestureStart; +} + +bool WindowContext::HasValidTransientUserGestureActivation() { + MOZ_ASSERT(IsInProcess()); + + if (GetUserActivationState() != UserActivation::State::FullActivated) { + // mUserGestureStart should be null if the document hasn't ever been + // activated by user gesture + MOZ_ASSERT_IF(GetUserActivationState() == UserActivation::State::None, + mUserGestureStart.IsNull()); + return false; + } + + MOZ_ASSERT(!mUserGestureStart.IsNull(), + "mUserGestureStart shouldn't be null if the document has ever " + "been activated by user gesture"); + TimeDuration timeout = TimeDuration::FromMilliseconds( + StaticPrefs::dom_user_activation_transient_timeout()); + + return timeout <= TimeDuration() || + (TimeStamp::Now() - mUserGestureStart) <= timeout; +} + +bool WindowContext::ConsumeTransientUserGestureActivation() { + MOZ_ASSERT(IsInProcess()); + MOZ_ASSERT(IsCurrent()); + + if (!HasValidTransientUserGestureActivation()) { + return false; + } + + BrowsingContext* top = mBrowsingContext->Top(); + top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) { + WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext(); + if (windowContext && windowContext->GetUserActivationState() == + UserActivation::State::FullActivated) { + Unused << windowContext->SetUserActivationState( + UserActivation::State::HasBeenActivated); + } + }); + + return true; +} + +bool WindowContext::CanShowPopup() { + uint32_t permit = GetPopupPermission(); + if (permit == nsIPermissionManager::ALLOW_ACTION) { + return true; + } + if (permit == nsIPermissionManager::DENY_ACTION) { + return false; + } + + return !StaticPrefs::dom_disable_open_during_load(); +} + +void WindowContext::TransientSetHasActivePeerConnections() { + if (!IsTop()) { + return; + } + + mFields.SetWithoutSyncing(true); +} + +WindowContext::IPCInitializer WindowContext::GetIPCInitializer() { + IPCInitializer init; + init.mInnerWindowId = mInnerWindowId; + init.mOuterWindowId = mOuterWindowId; + init.mBrowsingContextId = mBrowsingContext->Id(); + init.mFields = mFields.RawValues(); + return init; +} + +WindowContext::WindowContext(BrowsingContext* aBrowsingContext, + uint64_t aInnerWindowId, uint64_t aOuterWindowId, + FieldValues&& aInit) + : mFields(std::move(aInit)), + mInnerWindowId(aInnerWindowId), + mOuterWindowId(aOuterWindowId), + mBrowsingContext(aBrowsingContext) { + MOZ_ASSERT(mBrowsingContext); + MOZ_ASSERT(mInnerWindowId); + MOZ_ASSERT(mOuterWindowId); + RecomputeCanExecuteScripts(/* aApplyChanges */ false); +} + +WindowContext::~WindowContext() { + if (gWindowContexts) { + gWindowContexts->Remove(InnerWindowId()); + } +} + +JSObject* WindowContext::WrapObject(JSContext* cx, + JS::Handle aGivenProto) { + return WindowContext_Binding::Wrap(cx, this, aGivenProto); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowContext) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowContext) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowContext) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowContext) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowContext) + if (gWindowContexts) { + gWindowContexts->Remove(tmp->InnerWindowId()); + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonSyntheticChildren) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonSyntheticChildren) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits>::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::MaybeDiscarded& aParam) { + uint64_t id = aParam.ContextId(); + WriteIPDLParam(aWriter, aActor, id); +} + +bool IPDLParamTraits>::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + dom::MaybeDiscarded* aResult) { + uint64_t id = 0; + if (!ReadIPDLParam(aReader, aActor, &id)) { + return false; + } + + if (id == 0) { + *aResult = nullptr; + } else if (RefPtr wc = dom::WindowContext::GetById(id)) { + *aResult = std::move(wc); + } else { + aResult->SetDiscarded(id); + } + return true; +} + +void IPDLParamTraits::Write( + IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::WindowContext::IPCInitializer& aInit) { + // Write actor ID parameters. + WriteIPDLParam(aWriter, aActor, aInit.mInnerWindowId); + WriteIPDLParam(aWriter, aActor, aInit.mOuterWindowId); + WriteIPDLParam(aWriter, aActor, aInit.mBrowsingContextId); + WriteIPDLParam(aWriter, aActor, aInit.mFields); +} + +bool IPDLParamTraits::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + dom::WindowContext::IPCInitializer* aInit) { + // Read actor ID parameters. + return ReadIPDLParam(aReader, aActor, &aInit->mInnerWindowId) && + ReadIPDLParam(aReader, aActor, &aInit->mOuterWindowId) && + ReadIPDLParam(aReader, aActor, &aInit->mBrowsingContextId) && + ReadIPDLParam(aReader, aActor, &aInit->mFields); +} + +template struct IPDLParamTraits; + +} // namespace ipc +} // namespace mozilla diff --git a/docshell/base/WindowContext.h b/docshell/base/WindowContext.h new file mode 100644 index 0000000000..9396ad9ed1 --- /dev/null +++ b/docshell/base/WindowContext.h @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WindowContext_h +#define mozilla_dom_WindowContext_h + +#include "mozilla/PermissionDelegateHandler.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/Span.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/SyncedContext.h" +#include "mozilla/dom/UserActivation.h" +#include "nsDOMNavigationTiming.h" +#include "nsILoadInfo.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +class nsGlobalWindowInner; + +namespace mozilla { +class LogModule; + +namespace dom { + +class WindowGlobalChild; +class WindowGlobalParent; +class WindowGlobalInit; +class BrowsingContext; +class BrowsingContextGroup; + +#define MOZ_EACH_WC_FIELD(FIELD) \ + /* Whether the SHEntry associated with the current top-level \ + * window has already seen user interaction. \ + * As such, this will be reset to false when a new SHEntry is \ + * created without changing the WC (e.g. when using pushState or \ + * sub-frame navigation) \ + * This flag is set for optimization purposes, to avoid \ + * having to get the top SHEntry and update it on every \ + * user interaction. \ + * This is only meaningful on the top-level WC. */ \ + FIELD(SHEntryHasUserInteraction, bool) \ + FIELD(CookieBehavior, Maybe) \ + FIELD(IsOnContentBlockingAllowList, bool) \ + /* Whether the given window hierarchy is third party. See \ + * ThirdPartyUtil::IsThirdPartyWindow for details */ \ + FIELD(IsThirdPartyWindow, bool) \ + /* Whether this window's channel has been marked as a third-party \ + * tracking resource */ \ + FIELD(IsThirdPartyTrackingResourceWindow, bool) \ + FIELD(ShouldResistFingerprinting, bool) \ + FIELD(IsSecureContext, bool) \ + FIELD(IsOriginalFrameSource, bool) \ + /* Mixed-Content: If the corresponding documentURI is https, \ + * then this flag is true. */ \ + FIELD(IsSecure, bool) \ + /* Whether the user has overriden the mixed content blocker to allow \ + * mixed content loads to happen */ \ + FIELD(AllowMixedContent, bool) \ + /* Whether this window has registered a "beforeunload" event \ + * handler */ \ + FIELD(HasBeforeUnload, bool) \ + /* Controls whether the WindowContext is currently considered to be \ + * activated by a gesture */ \ + FIELD(UserActivationState, UserActivation::State) \ + FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy) \ + /* True if this document tree contained at least a HTMLMediaElement. \ + * This should only be set on top level context. */ \ + FIELD(DocTreeHadMedia, bool) \ + FIELD(AutoplayPermission, uint32_t) \ + FIELD(ShortcutsPermission, uint32_t) \ + /* Store the Id of the browsing context where active media session \ + * exists on the top level window context */ \ + FIELD(ActiveMediaSessionContextId, Maybe) \ + /* ALLOW_ACTION if it is allowed to open popups for the sub-tree \ + * starting and including the current WindowContext */ \ + FIELD(PopupPermission, uint32_t) \ + FIELD(DelegatedPermissions, \ + PermissionDelegateHandler::DelegatedPermissionList) \ + FIELD(DelegatedExactHostMatchPermissions, \ + PermissionDelegateHandler::DelegatedPermissionList) \ + FIELD(HasReportedShadowDOMUsage, bool) \ + /* Whether the principal of this window is for a local \ + * IP address */ \ + FIELD(IsLocalIP, bool) \ + /* Whether any of the windows in the subtree rooted at this window has \ + * active peer connections or not (only set on the top window). */ \ + FIELD(HasActivePeerConnections, bool) \ + /* Whether we can execute scripts in this WindowContext. Has no effect \ + * unless scripts are also allowed in the BrowsingContext. */ \ + FIELD(AllowJavascript, bool) \ + /* If this field is `true`, it means that this WindowContext's \ + * WindowState was saved to be stored in the legacy (non-SHIP) BFCache \ + * implementation. Always false for SHIP */ \ + FIELD(WindowStateSaved, bool) + +class WindowContext : public nsISupports, public nsWrapperCache { + MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowContext) + + public: + static already_AddRefed GetById(uint64_t aInnerWindowId); + static LogModule* GetLog(); + static LogModule* GetSyncLog(); + + BrowsingContext* GetBrowsingContext() const { return mBrowsingContext; } + BrowsingContextGroup* Group() const; + uint64_t Id() const { return InnerWindowId(); } + uint64_t InnerWindowId() const { return mInnerWindowId; } + uint64_t OuterWindowId() const { return mOuterWindowId; } + bool IsDiscarded() const { return mIsDiscarded; } + + // Returns `true` if this WindowContext is the current WindowContext in its + // BrowsingContext. + bool IsCurrent() const; + + // Returns `true` if this WindowContext is currently in the BFCache. + bool IsInBFCache(); + + bool IsInProcess() const { return mIsInProcess; } + + bool HasBeforeUnload() const { return GetHasBeforeUnload(); } + + bool IsLocalIP() const { return GetIsLocalIP(); } + + bool ShouldResistFingerprinting() const { + return GetShouldResistFingerprinting(); + } + + nsGlobalWindowInner* GetInnerWindow() const; + Document* GetDocument() const; + Document* GetExtantDoc() const; + + WindowGlobalChild* GetWindowGlobalChild() const; + + // Get the parent WindowContext of this WindowContext, taking the BFCache into + // account. This will not cross chrome/content boundaries. + WindowContext* GetParentWindowContext(); + WindowContext* TopWindowContext(); + + bool SameOriginWithTop() const; + + bool IsTop() const; + + Span> Children() { return mChildren; } + + // The filtered version of `Children()`, which contains no browsing contexts + // for synthetic documents as created by object loading content. + Span> NonSyntheticChildren() { + return mNonSyntheticChildren; + } + + // Cast this object to it's parent-process canonical form. + WindowGlobalParent* Canonical(); + + nsIGlobalObject* GetParentObject() const; + JSObject* WrapObject(JSContext* cx, + JS::Handle aGivenProto) override; + + void Discard(); + + struct IPCInitializer { + uint64_t mInnerWindowId; + uint64_t mOuterWindowId; + uint64_t mBrowsingContextId; + + FieldValues mFields; + }; + IPCInitializer GetIPCInitializer(); + + static void CreateFromIPC(IPCInitializer&& aInit); + + // Add new security state flags. + // These should be some of the nsIWebProgressListener 'HTTPS_ONLY_MODE' or + // 'MIXED' state flags, and should only be called on the top window context. + void AddSecurityState(uint32_t aStateFlags); + + // This function would be called when its corresponding window is activated + // by user gesture. + void NotifyUserGestureActivation(); + + // This function would be called when we want to reset the user gesture + // activation flag. + void NotifyResetUserGestureActivation(); + + // Return true if its corresponding window has been activated by user + // gesture. + bool HasBeenUserGestureActivated(); + + // Return true if its corresponding window has transient user gesture + // activation and the transient user gesture activation haven't yet timed + // out. + bool HasValidTransientUserGestureActivation(); + + // See `mUserGestureStart`. + const TimeStamp& GetUserGestureStart() const; + + // Return true if the corresponding window has valid transient user gesture + // activation and the transient user gesture activation had been consumed + // successfully. + bool ConsumeTransientUserGestureActivation(); + + bool CanShowPopup(); + + bool AllowJavascript() const { return GetAllowJavascript(); } + bool CanExecuteScripts() const { return mCanExecuteScripts; } + + void TransientSetHasActivePeerConnections(); + + protected: + WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId, FieldValues&& aFields); + virtual ~WindowContext(); + + virtual void Init(); + + private: + friend class BrowsingContext; + friend class WindowGlobalChild; + friend class WindowGlobalActor; + + void AppendChildBrowsingContext(BrowsingContext* aBrowsingContext); + void RemoveChildBrowsingContext(BrowsingContext* aBrowsingContext); + + // Update non-synthetic children based on whether `aBrowsingContext` + // is synthetic or not. Regardless the synthetic of `aBrowsingContext`, it is + // kept in this WindowContext's all children list. + void UpdateChildSynthetic(BrowsingContext* aBrowsingContext, + bool aIsSynthetic); + + // Send a given `BaseTransaction` object to the correct remote. + void SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, uint64_t aEpoch); + void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, + uint64_t aEpoch); + + bool CheckOnlyOwningProcessCanSet(ContentParent* aSource); + + // Overload `CanSet` to get notifications for a particular field being set. + bool CanSet(FieldIndex, const bool& aIsSecure, + ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aAllowMixedContent, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aHasBeforeUnload, + ContentParent* aSource); + + bool CanSet(FieldIndex, const Maybe& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return true; + } + + bool CanSet(FieldIndex, + const bool& IsThirdPartyWindow, ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aIsThirdPartyTrackingResourceWindow, + ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aShouldResistFingerprinting, ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aIsSecureContext, + ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aIsOriginalFrameSource, ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, + const Maybe& aValue, ContentParent* aSource); + bool CanSet(FieldIndex, const uint32_t&, + ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aSHEntryHasUserInteraction, ContentParent* aSource) { + return true; + } + bool CanSet(FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, + const UserActivation::State& aUserActivationState, + ContentParent* aSource) { + return true; + } + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return true; + } + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, bool aValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, bool, ContentParent*); + + void DidSet(FieldIndex, bool aOldValue); + + void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, bool aValue, + ContentParent* aSource); + + // Overload `DidSet` to get notifications for a particular field being set. + // + // You can also overload the variant that gets the old value if you need it. + template + void DidSet(FieldIndex) {} + template + void DidSet(FieldIndex, T&& aOldValue) {} + void DidSet(FieldIndex); + + // Recomputes whether we can execute scripts in this WindowContext based on + // the value of AllowJavascript() and whether scripts are allowed in the + // BrowsingContext. + void RecomputeCanExecuteScripts(bool aApplyChanges = true); + + const uint64_t mInnerWindowId; + const uint64_t mOuterWindowId; + RefPtr mBrowsingContext; + WeakPtr mWindowGlobalChild; + + // --- NEVER CHANGE `mChildren` DIRECTLY! --- + // Changes to this list need to be synchronized to the list within our + // `mBrowsingContext`, and should only be performed through the + // `AppendChildBrowsingContext` and `RemoveChildBrowsingContext` methods. + nsTArray> mChildren; + + // --- NEVER CHANGE `mNonSyntheticChildren` DIRECTLY! --- + // Same reason as for mChildren. + // mNonSyntheticChildren contains the same browsing contexts except browsing + // contexts created by the synthetic document for object loading contents + // loading images. This is used to discern browsing contexts created when + // loading images in or elements, so that they can be hidden + // from named targeting, `Window.frames` etc. + nsTArray> mNonSyntheticChildren; + + bool mIsDiscarded = false; + bool mIsInProcess = false; + + // Determines if we can execute scripts in this WindowContext. True if + // AllowJavascript() is true and script execution is allowed in the + // BrowsingContext. + bool mCanExecuteScripts = true; + + // The start time of user gesture, this is only available if the window + // context is in process. + TimeStamp mUserGestureStart; +}; + +using WindowContextTransaction = WindowContext::BaseTransaction; +using WindowContextInitializer = WindowContext::IPCInitializer; +using MaybeDiscardedWindowContext = MaybeDiscarded; + +// Don't specialize the `Transaction` object for every translation unit it's +// used in. This should help keep code size down. +extern template class syncedcontext::Transaction; + +} // namespace dom + +namespace ipc { +template <> +struct IPDLParamTraits> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::MaybeDiscarded& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + dom::MaybeDiscarded* aResult); +}; + +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const dom::WindowContext::IPCInitializer& aInitializer); + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + dom::WindowContext::IPCInitializer* aInitializer); +}; +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_WindowContext_h) diff --git a/docshell/base/crashtests/1257730-1.html b/docshell/base/crashtests/1257730-1.html new file mode 100644 index 0000000000..028a1adb88 --- /dev/null +++ b/docshell/base/crashtests/1257730-1.html @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/docshell/base/crashtests/1331295.html b/docshell/base/crashtests/1331295.html new file mode 100644 index 0000000000..cdcb29e7fe --- /dev/null +++ b/docshell/base/crashtests/1331295.html @@ -0,0 +1,25 @@ + + + + + + + +
+ + + + diff --git a/docshell/base/crashtests/1341657.html b/docshell/base/crashtests/1341657.html new file mode 100644 index 0000000000..d68fa1eb03 --- /dev/null +++ b/docshell/base/crashtests/1341657.html @@ -0,0 +1,18 @@ + + + + + + diff --git a/docshell/base/crashtests/1584467.html b/docshell/base/crashtests/1584467.html new file mode 100644 index 0000000000..5509808bcc --- /dev/null +++ b/docshell/base/crashtests/1584467.html @@ -0,0 +1,12 @@ + + + diff --git a/docshell/base/crashtests/1614211-1.html b/docshell/base/crashtests/1614211-1.html new file mode 100644 index 0000000000..1d683e0714 --- /dev/null +++ b/docshell/base/crashtests/1614211-1.html @@ -0,0 +1,15 @@ + + + diff --git a/docshell/base/crashtests/1617315-1.html b/docshell/base/crashtests/1617315-1.html new file mode 100644 index 0000000000..05d9a704dc --- /dev/null +++ b/docshell/base/crashtests/1617315-1.html @@ -0,0 +1,8 @@ + + diff --git a/docshell/base/crashtests/1667491.html b/docshell/base/crashtests/1667491.html new file mode 100644 index 0000000000..ecc77a5e9b --- /dev/null +++ b/docshell/base/crashtests/1667491.html @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/docshell/base/crashtests/1667491_1.html b/docshell/base/crashtests/1667491_1.html new file mode 100644 index 0000000000..3df3353f72 --- /dev/null +++ b/docshell/base/crashtests/1667491_1.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/docshell/base/crashtests/1672873.html b/docshell/base/crashtests/1672873.html new file mode 100644 index 0000000000..33aa92f0ef --- /dev/null +++ b/docshell/base/crashtests/1672873.html @@ -0,0 +1,6 @@ + diff --git a/docshell/base/crashtests/1690169-1.html b/docshell/base/crashtests/1690169-1.html new file mode 100644 index 0000000000..6c9be20be3 --- /dev/null +++ b/docshell/base/crashtests/1690169-1.html @@ -0,0 +1,11 @@ + + diff --git a/docshell/base/crashtests/1753136.html b/docshell/base/crashtests/1753136.html new file mode 100644 index 0000000000..22f679309e --- /dev/null +++ b/docshell/base/crashtests/1753136.html @@ -0,0 +1,2 @@ + + diff --git a/docshell/base/crashtests/1804803.html b/docshell/base/crashtests/1804803.html new file mode 100644 index 0000000000..5103c00416 --- /dev/null +++ b/docshell/base/crashtests/1804803.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/docshell/base/crashtests/1804803.sjs b/docshell/base/crashtests/1804803.sjs new file mode 100644 index 0000000000..0486e32048 --- /dev/null +++ b/docshell/base/crashtests/1804803.sjs @@ -0,0 +1,18 @@ +function handleRequest(request, response) { + let counter = Number(getState("load")); + const reload = counter == 0 ? "self.history.go(0);" : ""; + setState("load", String(++counter)); + const document = ` + +`; + + response.write(document); +} diff --git a/docshell/base/crashtests/369126-1.html b/docshell/base/crashtests/369126-1.html new file mode 100644 index 0000000000..e9dacec301 --- /dev/null +++ b/docshell/base/crashtests/369126-1.html @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/docshell/base/crashtests/40929-1-inner.html b/docshell/base/crashtests/40929-1-inner.html new file mode 100644 index 0000000000..313046a348 --- /dev/null +++ b/docshell/base/crashtests/40929-1-inner.html @@ -0,0 +1,14 @@ +Infinite Loop + + + + + diff --git a/docshell/base/crashtests/40929-1.html b/docshell/base/crashtests/40929-1.html new file mode 100644 index 0000000000..90685d9f1f --- /dev/null +++ b/docshell/base/crashtests/40929-1.html @@ -0,0 +1,6 @@ + +Infinite Loop + + + + diff --git a/docshell/base/crashtests/430124-1.html b/docshell/base/crashtests/430124-1.html new file mode 100644 index 0000000000..8cdbc1d077 --- /dev/null +++ b/docshell/base/crashtests/430124-1.html @@ -0,0 +1,5 @@ + + + +
+ diff --git a/docshell/base/crashtests/430628-1.html b/docshell/base/crashtests/430628-1.html new file mode 100644 index 0000000000..4a68a5a015 --- /dev/null +++ b/docshell/base/crashtests/430628-1.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docshell/base/crashtests/432114-1.html b/docshell/base/crashtests/432114-1.html new file mode 100644 index 0000000000..8878d6605a --- /dev/null +++ b/docshell/base/crashtests/432114-1.html @@ -0,0 +1,8 @@ + + +Bug - Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable + + + + + diff --git a/docshell/base/crashtests/432114-2.html b/docshell/base/crashtests/432114-2.html new file mode 100644 index 0000000000..da77287b61 --- /dev/null +++ b/docshell/base/crashtests/432114-2.html @@ -0,0 +1,21 @@ + + +testcase2 Bug 432114 � Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable + + + + + + + diff --git a/docshell/base/crashtests/436900-1-inner.html b/docshell/base/crashtests/436900-1-inner.html new file mode 100644 index 0000000000..6fe35ccb1a --- /dev/null +++ b/docshell/base/crashtests/436900-1-inner.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/docshell/base/crashtests/436900-1.html b/docshell/base/crashtests/436900-1.html new file mode 100644 index 0000000000..582d1919d1 --- /dev/null +++ b/docshell/base/crashtests/436900-1.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docshell/base/crashtests/436900-2-inner.html b/docshell/base/crashtests/436900-2-inner.html new file mode 100644 index 0000000000..ea79f75e88 --- /dev/null +++ b/docshell/base/crashtests/436900-2-inner.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/docshell/base/crashtests/436900-2.html b/docshell/base/crashtests/436900-2.html new file mode 100644 index 0000000000..2e1f0c1def --- /dev/null +++ b/docshell/base/crashtests/436900-2.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docshell/base/crashtests/443655.html b/docshell/base/crashtests/443655.html new file mode 100644 index 0000000000..ce0a8c18b8 --- /dev/null +++ b/docshell/base/crashtests/443655.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docshell/base/crashtests/500328-1.html b/docshell/base/crashtests/500328-1.html new file mode 100644 index 0000000000..fd97f84ae1 --- /dev/null +++ b/docshell/base/crashtests/500328-1.html @@ -0,0 +1,17 @@ + + + + + + diff --git a/docshell/base/crashtests/514779-1.xhtml b/docshell/base/crashtests/514779-1.xhtml new file mode 100644 index 0000000000..16ac3d9d66 --- /dev/null +++ b/docshell/base/crashtests/514779-1.xhtml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/docshell/base/crashtests/678872-1.html b/docshell/base/crashtests/678872-1.html new file mode 100644 index 0000000000..294b3e689b --- /dev/null +++ b/docshell/base/crashtests/678872-1.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/docshell/base/crashtests/914521.html b/docshell/base/crashtests/914521.html new file mode 100644 index 0000000000..f30d78c10f --- /dev/null +++ b/docshell/base/crashtests/914521.html @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/docshell/base/crashtests/crashtests.list b/docshell/base/crashtests/crashtests.list new file mode 100644 index 0000000000..f9b214bfa2 --- /dev/null +++ b/docshell/base/crashtests/crashtests.list @@ -0,0 +1,25 @@ +load 40929-1.html +load 369126-1.html +load 430124-1.html +load 430628-1.html +load 432114-1.html +load 432114-2.html +load 436900-1.html +asserts(0-1) load 436900-2.html # bug 566159 +load 443655.html +load 500328-1.html +load 514779-1.xhtml +load 614499-1.html +load 678872-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 914521.html # Android bug 1584562 +pref(browser.send_pings,true) asserts(0-2) load 1257730-1.html # bug 566159 +load 1331295.html +load 1341657.html +load 1584467.html +load 1614211-1.html +load 1617315-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 1667491.html +pref(dom.disable_open_during_load,false) load 1690169-1.html +load 1672873.html +load 1753136.html +HTTP load 1804803.html diff --git a/docshell/base/crashtests/file_432114-2.xhtml b/docshell/base/crashtests/file_432114-2.xhtml new file mode 100644 index 0000000000..40bf886b8e --- /dev/null +++ b/docshell/base/crashtests/file_432114-2.xhtml @@ -0,0 +1 @@ + diff --git a/docshell/base/moz.build b/docshell/base/moz.build new file mode 100644 index 0000000000..049a78d440 --- /dev/null +++ b/docshell/base/moz.build @@ -0,0 +1,130 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Navigation") + +with Files("crashtests/430628*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("crashtests/432114*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("crashtests/500328*"): + BUG_COMPONENT = ("Firefox", "Bookmarks & History") + +with Files("IHistory.h"): + BUG_COMPONENT = ("Toolkit", "Places") + +with Files("*LoadContext.*"): + BUG_COMPONENT = ("Core", "Networking") + +with Files("nsAboutRedirector.*"): + BUG_COMPONENT = ("Core", "General") + +with Files("nsIScrollObserver.*"): + BUG_COMPONENT = ("Core", "Panning and Zooming") + +DIRS += [ + "timeline", +] + +XPIDL_SOURCES += [ + "nsIContentViewer.idl", + "nsIContentViewerEdit.idl", + "nsIDocShell.idl", + "nsIDocShellTreeItem.idl", + "nsIDocShellTreeOwner.idl", + "nsIDocumentLoaderFactory.idl", + "nsILoadContext.idl", + "nsILoadURIDelegate.idl", + "nsIPrivacyTransitionObserver.idl", + "nsIReflowObserver.idl", + "nsIRefreshURI.idl", + "nsITooltipListener.idl", + "nsITooltipTextProvider.idl", + "nsIURIFixup.idl", + "nsIWebNavigation.idl", + "nsIWebNavigationInfo.idl", + "nsIWebPageDescriptor.idl", +] + +XPIDL_MODULE = "docshell" + +EXPORTS += [ + "nsCTooltipTextProvider.h", + "nsDocShell.h", + "nsDocShellLoadState.h", + "nsDocShellLoadTypes.h", + "nsDocShellTreeOwner.h", + "nsDSURIContentListener.h", + "nsIScrollObserver.h", + "nsWebNavigationInfo.h", + "SerializedLoadContext.h", +] + +EXPORTS.mozilla += [ + "BaseHistory.h", + "IHistory.h", + "LoadContext.h", +] + +EXPORTS.mozilla.dom += [ + "BrowsingContext.h", + "BrowsingContextGroup.h", + "BrowsingContextWebProgress.h", + "CanonicalBrowsingContext.h", + "ChildProcessChannelListener.h", + "SyncedContext.h", + "SyncedContextInlines.h", + "WindowContext.h", +] + +UNIFIED_SOURCES += [ + "BaseHistory.cpp", + "BrowsingContext.cpp", + "BrowsingContextGroup.cpp", + "BrowsingContextWebProgress.cpp", + "CanonicalBrowsingContext.cpp", + "ChildProcessChannelListener.cpp", + "LoadContext.cpp", + "nsAboutRedirector.cpp", + "nsDocShell.cpp", + "nsDocShellEditorData.cpp", + "nsDocShellEnumerator.cpp", + "nsDocShellLoadState.cpp", + "nsDocShellTelemetryUtils.cpp", + "nsDocShellTreeOwner.cpp", + "nsDSURIContentListener.cpp", + "nsPingListener.cpp", + "nsRefreshTimer.cpp", + "nsWebNavigationInfo.cpp", + "SerializedLoadContext.cpp", + "WindowContext.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "/docshell/shistory", + "/dom/base", + "/dom/bindings", + "/js/xpconnect/src", + "/layout/base", + "/layout/generic", + "/layout/style", + "/layout/xul", + "/netwerk/base", + "/netwerk/protocol/viewsource", + "/toolkit/components/browser", + "/toolkit/components/find", + "/tools/profiler", +] + +EXTRA_JS_MODULES += ["URIFixup.sys.mjs"] + +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp new file mode 100644 index 0000000000..faf334f345 --- /dev/null +++ b/docshell/base/nsAboutRedirector.cpp @@ -0,0 +1,299 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAboutRedirector.h" +#include "nsNetUtil.h" +#include "nsAboutProtocolUtils.h" +#include "nsBaseChannel.h" +#include "mozilla/ArrayUtils.h" +#include "nsIProtocolHandler.h" +#include "nsXULAppAPI.h" +#include "mozilla/Preferences.h" +#include "mozilla/gfx/GPUProcessManager.h" + +#define ABOUT_CONFIG_ENABLED_PREF "general.aboutConfig.enable" + +NS_IMPL_ISUPPORTS(nsAboutRedirector, nsIAboutModule) + +struct RedirEntry { + const char* id; + const char* url; + uint32_t flags; +}; + +class CrashChannel final : public nsBaseChannel { + public: + explicit CrashChannel(nsIURI* aURI) { SetURI(aURI); } + + nsresult OpenContentStream(bool async, nsIInputStream** stream, + nsIChannel** channel) override { + nsAutoCString spec; + mURI->GetSpec(spec); + + if (spec.EqualsASCII("about:crashparent") && XRE_IsParentProcess()) { + MOZ_CRASH("Crash via about:crashparent"); + } + + if (spec.EqualsASCII("about:crashgpu") && XRE_IsParentProcess()) { + if (auto* gpu = mozilla::gfx::GPUProcessManager::Get()) { + gpu->CrashProcess(); + } + } + + if (spec.EqualsASCII("about:crashcontent") && XRE_IsContentProcess()) { + MOZ_CRASH("Crash via about:crashcontent"); + } + + NS_WARNING("Unhandled about:crash* URI or wrong process"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + protected: + virtual ~CrashChannel() = default; +}; + +/* + Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome + privileges. This is potentially dangerous. Please use + URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below + unless your about: page really needs chrome privileges. Security review is + required before adding new map entries without + URI_SAFE_FOR_UNTRUSTED_CONTENT. + + URI_SAFE_FOR_UNTRUSTED_CONTENT is not enough to let web pages load that page, + for that you need MAKE_LINKABLE. + + NOTE: changes to this redir map need to be accompanied with changes to + docshell/build/components.conf + */ +static const RedirEntry kRedirMap[] = { + {"about", "chrome://global/content/aboutAbout.html", 0}, + {"addons", "chrome://mozapps/content/extensions/aboutaddons.html", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}, + {"buildconfig", "chrome://global/content/buildconfig.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::IS_SECURE_CHROME_UI}, + {"checkerboard", "chrome://global/content/aboutCheckerboard.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT}, +#ifndef MOZ_WIDGET_ANDROID + {"config", "chrome://global/content/aboutconfig/aboutconfig.html", + nsIAboutModule::IS_SECURE_CHROME_UI}, +#else + {"config", "chrome://geckoview/content/config.xhtml", + nsIAboutModule::IS_SECURE_CHROME_UI}, +#endif +#ifdef MOZ_CRASHREPORTER + {"crashes", "chrome://global/content/crashes.html", + nsIAboutModule::IS_SECURE_CHROME_UI}, +#endif + {"credits", "https://www.mozilla.org/credits/", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"httpsonlyerror", "chrome://global/content/httpsonlyerror/errorpage.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"license", "chrome://global/content/license.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::IS_SECURE_CHROME_UI}, + {"logging", "chrome://global/content/aboutLogging.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"logo", "chrome://branding/content/about.png", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + // Linkable for testing reasons. + nsIAboutModule::MAKE_LINKABLE}, + {"memory", "chrome://global/content/aboutMemory.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"certificate", "chrome://global/content/certviewer/certviewer.html", + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | + nsIAboutModule::IS_SECURE_CHROME_UI}, + {"mozilla", "chrome://global/content/mozilla.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"neterror", "chrome://global/content/aboutNetError.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"networking", "chrome://global/content/aboutNetworking.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"performance", "chrome://global/content/aboutPerformance.html", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}, +#ifndef ANDROID + {"plugins", "chrome://global/content/plugins.html", + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::IS_SECURE_CHROME_UI}, +#endif + {"processes", "chrome://global/content/aboutProcesses.html", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}, + // about:serviceworkers always wants to load in the parent process because + // the only place nsIServiceWorkerManager has any data is in the parent + // process. + // + // There is overlap without about:debugging, but about:debugging is not + // available on mobile at this time, and it's useful to be able to know if + // a ServiceWorker is registered directly from the mobile browser without + // having to connect the device to a desktop machine and all that entails. + {"serviceworkers", "chrome://global/content/aboutServiceWorkers.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#ifndef ANDROID + {"profiles", "chrome://global/content/aboutProfiles.xhtml", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}, +#endif + // about:srcdoc is unresolvable by specification. It is included here + // because the security manager would disallow srcdoc iframes otherwise. + {"srcdoc", "about:blank", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + // Needs to be linkable so content can touch its own srcdoc frames + nsIAboutModule::MAKE_LINKABLE | nsIAboutModule::URI_CAN_LOAD_IN_CHILD}, + {"support", "chrome://global/content/aboutSupport.xhtml", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}, +#ifdef XP_WIN + {"third-party", "chrome://global/content/aboutThirdParty.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"windows-messages", "chrome://global/content/aboutWindowsMessages.html", + nsIAboutModule::ALLOW_SCRIPT}, +#endif +#ifndef MOZ_GLEAN_ANDROID + {"glean", "chrome://global/content/aboutGlean.html", +# if !defined(NIGHTLY_BUILD) && defined(MOZILLA_OFFICIAL) + nsIAboutModule::HIDE_FROM_ABOUTABOUT | +# endif + nsIAboutModule::ALLOW_SCRIPT}, +#endif + {"telemetry", "chrome://global/content/aboutTelemetry.xhtml", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}, + {"translations", "chrome://global/content/translations/translations.html", + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"url-classifier", "chrome://global/content/aboutUrlClassifier.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"crashparent", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"crashcontent", "about:blank", + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}}; +static const int kRedirTotal = mozilla::ArrayLength(kRedirMap); + +NS_IMETHODIMP +nsAboutRedirector::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** aResult) { + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aLoadInfo); + NS_ASSERTION(aResult, "must not be null"); + + nsAutoCString path; + nsresult rv = NS_GetAboutModuleName(aURI, path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent") || + path.EqualsASCII("crashgpu")) { + bool isExternal; + aLoadInfo->GetLoadTriggeredFromExternal(&isExternal); + if (isExternal) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr channel = new CrashChannel(aURI); + channel->SetLoadInfo(aLoadInfo); + channel.forget(aResult); + return NS_OK; + } + + if (path.EqualsASCII("config") && + !mozilla::Preferences::GetBool(ABOUT_CONFIG_ENABLED_PREF, true)) { + return NS_ERROR_NOT_AVAILABLE; + } + + for (int i = 0; i < kRedirTotal; i++) { + if (!strcmp(path.get(), kRedirMap[i].id)) { + nsCOMPtr tempChannel; + nsCOMPtr tempURI; + rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewChannelInternal(getter_AddRefs(tempChannel), tempURI, + aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + // If tempURI links to an external URI (i.e. something other than + // chrome:// or resource://) then set result principal URI on the + // load info which forces the channel principal to reflect the displayed + // URL rather then being the systemPrincipal. + bool isUIResource = false; + rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isUIResource); + NS_ENSURE_SUCCESS(rv, rv); + + bool isAboutBlank = NS_IsAboutBlank(tempURI); + + if (!isUIResource && !isAboutBlank) { + aLoadInfo->SetResultPrincipalURI(tempURI); + } + + tempChannel->SetOriginalURI(aURI); + + tempChannel.forget(aResult); + return rv; + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +NS_IMETHODIMP +nsAboutRedirector::GetURIFlags(nsIURI* aURI, uint32_t* aResult) { + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString name; + nsresult rv = NS_GetAboutModuleName(aURI, name); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < kRedirTotal; i++) { + if (name.EqualsASCII(kRedirMap[i].id)) { + *aResult = kRedirMap[i].flags; + return NS_OK; + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +NS_IMETHODIMP +nsAboutRedirector::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString name; + nsresult rv = NS_GetAboutModuleName(aURI, name); + NS_ENSURE_SUCCESS(rv, rv); + + for (const auto& redir : kRedirMap) { + if (name.EqualsASCII(redir.id)) { + return NS_NewURI(chromeURI, redir.url); + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +nsresult nsAboutRedirector::Create(REFNSIID aIID, void** aResult) { + RefPtr about = new nsAboutRedirector(); + return about->QueryInterface(aIID, aResult); +} diff --git a/docshell/base/nsAboutRedirector.h b/docshell/base/nsAboutRedirector.h new file mode 100644 index 0000000000..0bf021cc31 --- /dev/null +++ b/docshell/base/nsAboutRedirector.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAboutRedirector_h__ +#define nsAboutRedirector_h__ + +#include "nsIAboutModule.h" + +class nsAboutRedirector : public nsIAboutModule { + public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIABOUTMODULE + + nsAboutRedirector() {} + + static nsresult Create(REFNSIID aIID, void** aResult); + + protected: + virtual ~nsAboutRedirector() {} +}; + +#endif // nsAboutRedirector_h__ diff --git a/docshell/base/nsCTooltipTextProvider.h b/docshell/base/nsCTooltipTextProvider.h new file mode 100644 index 0000000000..731edf1170 --- /dev/null +++ b/docshell/base/nsCTooltipTextProvider.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSCTOOLTIPTEXTPROVIDER_H +#define NSCTOOLTIPTEXTPROVIDER_H + +#define NS_TOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/tooltiptextprovider;1" +#define NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/default-tooltiptextprovider;1" + +#endif diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp new file mode 100644 index 0000000000..437f729fc0 --- /dev/null +++ b/docshell/base/nsDSURIContentListener.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" +#include "nsDSURIContentListener.h" +#include "nsIChannel.h" +#include "nsServiceManagerUtils.h" +#include "nsDocShellCID.h" +#include "nsIWebNavigationInfo.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/Unused.h" +#include "nsError.h" +#include "nsContentSecurityManager.h" +#include "nsDocShellLoadTypes.h" +#include "nsGlobalWindowOuter.h" +#include "nsIInterfaceRequestor.h" +#include "nsIMultiPartChannel.h" +#include "nsWebNavigationInfo.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ADDREF(MaybeCloseWindowHelper) +NS_IMPL_RELEASE(MaybeCloseWindowHelper) + +NS_INTERFACE_MAP_BEGIN(MaybeCloseWindowHelper) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsINamed) +NS_INTERFACE_MAP_END + +MaybeCloseWindowHelper::MaybeCloseWindowHelper(BrowsingContext* aContentContext) + : mBrowsingContext(aContentContext), + mTimer(nullptr), + mShouldCloseWindow(false) {} + +MaybeCloseWindowHelper::~MaybeCloseWindowHelper() {} + +void MaybeCloseWindowHelper::SetShouldCloseWindow(bool aShouldCloseWindow) { + mShouldCloseWindow = aShouldCloseWindow; +} + +BrowsingContext* MaybeCloseWindowHelper::MaybeCloseWindow() { + if (!mShouldCloseWindow) { + return mBrowsingContext; + } + + // This method should not be called more than once, but it's better to avoid + // closing the current window again. + mShouldCloseWindow = false; + + // Reset the window context to the opener window so that the dependent + // dialogs have a parent + RefPtr newBC = ChooseNewBrowsingContext(mBrowsingContext); + + if (newBC != mBrowsingContext && newBC && !newBC->IsDiscarded()) { + mBCToClose = mBrowsingContext; + mBrowsingContext = newBC; + + // Now close the old window. Do it on a timer so that we don't run + // into issues trying to close the window before it has fully opened. + NS_ASSERTION(!mTimer, "mTimer was already initialized once!"); + NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, 0, + nsITimer::TYPE_ONE_SHOT); + } + + return mBrowsingContext; +} + +already_AddRefed +MaybeCloseWindowHelper::ChooseNewBrowsingContext(BrowsingContext* aBC) { + RefPtr opener = aBC->GetOpener(); + if (opener && !opener->IsDiscarded()) { + return opener.forget(); + } + + if (!XRE_IsParentProcess()) { + return nullptr; + } + + opener = BrowsingContext::Get(aBC->Canonical()->GetCrossGroupOpenerId()); + if (!opener || opener->IsDiscarded()) { + return nullptr; + } + return opener.forget(); +} + +NS_IMETHODIMP +MaybeCloseWindowHelper::Notify(nsITimer* timer) { + NS_ASSERTION(mBCToClose, "No window to close after timer fired"); + + mBCToClose->Close(CallerType::System, IgnoreErrors()); + mBCToClose = nullptr; + mTimer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +MaybeCloseWindowHelper::GetName(nsACString& aName) { + aName.AssignLiteral("MaybeCloseWindowHelper"); + return NS_OK; +} + +nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell) + : mDocShell(aDocShell), + mExistingJPEGRequest(nullptr), + mParentContentListener(nullptr) {} + +nsDSURIContentListener::~nsDSURIContentListener() {} + +NS_IMPL_ADDREF(nsDSURIContentListener) +NS_IMPL_RELEASE(nsDSURIContentListener) + +NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsDSURIContentListener::DoContent(const nsACString& aContentType, + bool aIsContentPreferred, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler, + bool* aAbortProcess) { + nsresult rv; + NS_ENSURE_ARG_POINTER(aContentHandler); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + RefPtr docShell = mDocShell; + + *aAbortProcess = false; + + // determine if the channel has just been retargeted to us... + nsLoadFlags loadFlags = 0; + if (nsCOMPtr openedChannel = do_QueryInterface(aRequest)) { + openedChannel->GetLoadFlags(&loadFlags); + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + // XXX: Why does this not stop the content too? + docShell->Stop(nsIWebNavigation::STOP_NETWORK); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + docShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL); + } + + // In case of multipart jpeg request (mjpeg) we don't really want to + // create new viewer since the one we already have is capable of + // rendering multipart jpeg correctly (see bug 625012) + nsCOMPtr baseChannel; + if (nsCOMPtr mpchan = do_QueryInterface(aRequest)) { + mpchan->GetBaseChannel(getter_AddRefs(baseChannel)); + } + + bool reuseCV = baseChannel && baseChannel == mExistingJPEGRequest && + aContentType.EqualsLiteral("image/jpeg"); + + if (mExistingJPEGStreamListener && reuseCV) { + RefPtr copy(mExistingJPEGStreamListener); + copy.forget(aContentHandler); + rv = NS_OK; + } else { + rv = docShell->CreateContentViewer(aContentType, aRequest, aContentHandler); + if (NS_SUCCEEDED(rv) && reuseCV) { + mExistingJPEGStreamListener = *aContentHandler; + } else { + mExistingJPEGStreamListener = nullptr; + } + mExistingJPEGRequest = baseChannel; + } + + if (rv == NS_ERROR_DOCSHELL_DYING) { + aRequest->Cancel(rv); + *aAbortProcess = true; + return NS_OK; + } + + if (NS_FAILED(rv)) { + // we don't know how to handle the content + nsCOMPtr forget = dont_AddRef(*aContentHandler); + *aContentHandler = nullptr; + return rv; + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + nsCOMPtr domWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + domWindow->Focus(mozilla::dom::CallerType::System); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::IsPreferred(const char* aContentType, + char** aDesiredContentType, + bool* aCanHandle) { + NS_ENSURE_ARG_POINTER(aCanHandle); + NS_ENSURE_ARG_POINTER(aDesiredContentType); + + // the docshell has no idea if it is the preferred content provider or not. + // It needs to ask its parent if it is the preferred content handler or not... + + nsCOMPtr parentListener; + GetParentContentListener(getter_AddRefs(parentListener)); + if (parentListener) { + return parentListener->IsPreferred(aContentType, aDesiredContentType, + aCanHandle); + } + // we used to return false here if we didn't have a parent properly registered + // at the top of the docshell hierarchy to dictate what content types this + // docshell should be a preferred handler for. But this really makes it hard + // for developers using iframe or browser tags because then they need to make + // sure they implement nsIURIContentListener otherwise all link clicks would + // get sent to another window because we said we weren't the preferred handler + // type. I'm going to change the default now... if we can handle the content, + // and someone didn't EXPLICITLY set a nsIURIContentListener at the top of our + // docshell chain, then we'll now always attempt to process the content + // ourselves... + return CanHandleContent(aContentType, true, aDesiredContentType, aCanHandle); +} + +NS_IMETHODIMP +nsDSURIContentListener::CanHandleContent(const char* aContentType, + bool aIsContentPreferred, + char** aDesiredContentType, + bool* aCanHandleContent) { + MOZ_ASSERT(aCanHandleContent, "Null out param?"); + NS_ENSURE_ARG_POINTER(aDesiredContentType); + + *aCanHandleContent = false; + *aDesiredContentType = nullptr; + + if (aContentType) { + uint32_t canHandle = + nsWebNavigationInfo::IsTypeSupported(nsDependentCString(aContentType)); + *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetLoadCookie(nsISupports** aLoadCookie) { + NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell)); + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetLoadCookie(nsISupports* aLoadCookie) { +#ifdef DEBUG + RefPtr cookieAsDocLoader = + nsDocLoader::GetAsDocLoader(aLoadCookie); + NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell, + "Invalid load cookie being set!"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetParentContentListener( + nsIURIContentListener** aParentListener) { + if (mWeakParentContentListener) { + nsCOMPtr tempListener = + do_QueryReferent(mWeakParentContentListener); + *aParentListener = tempListener; + NS_IF_ADDREF(*aParentListener); + } else { + *aParentListener = mParentContentListener; + NS_IF_ADDREF(*aParentListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetParentContentListener( + nsIURIContentListener* aParentListener) { + if (aParentListener) { + // Store the parent listener as a weak ref. Parents not supporting + // nsISupportsWeakReference assert but may still be used. + mParentContentListener = nullptr; + mWeakParentContentListener = do_GetWeakReference(aParentListener); + if (!mWeakParentContentListener) { + mParentContentListener = aParentListener; + } + } else { + mWeakParentContentListener = nullptr; + mParentContentListener = nullptr; + } + return NS_OK; +} diff --git a/docshell/base/nsDSURIContentListener.h b/docshell/base/nsDSURIContentListener.h new file mode 100644 index 0000000000..61ed36456f --- /dev/null +++ b/docshell/base/nsDSURIContentListener.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDSURIContentListener_h__ +#define nsDSURIContentListener_h__ + +#include "nsCOMPtr.h" +#include "nsIURIContentListener.h" +#include "nsWeakReference.h" +#include "nsITimer.h" + +class nsDocShell; +class nsIInterfaceRequestor; +class nsIWebNavigationInfo; +class nsPIDOMWindowOuter; + +// Helper Class to eventually close an already opened window +class MaybeCloseWindowHelper final : public nsITimerCallback, public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + explicit MaybeCloseWindowHelper( + mozilla::dom::BrowsingContext* aContentContext); + + /** + * Closes the provided window async (if mShouldCloseWindow is true) and + * returns a valid browsingContext to be used instead as parent for dialogs or + * similar things. + * In case mShouldCloseWindow is true, the returned BrowsingContext will be + * the window's opener (or original cross-group opener in the case of a + * `noopener` popup). + */ + mozilla::dom::BrowsingContext* MaybeCloseWindow(); + + void SetShouldCloseWindow(bool aShouldCloseWindow); + + protected: + ~MaybeCloseWindowHelper(); + + private: + already_AddRefed ChooseNewBrowsingContext( + mozilla::dom::BrowsingContext* aBC); + + /** + * The dom window associated to handle content. + */ + RefPtr mBrowsingContext; + + /** + * Used to close the window on a timer, to avoid any exceptions that are + * thrown if we try to close the window before it's fully loaded. + */ + RefPtr mBCToClose; + nsCOMPtr mTimer; + + /** + * This is set based on whether the channel indicates that a new window + * was opened, e.g. for a download, or was blocked. If so, then we + * close it. + */ + bool mShouldCloseWindow; +}; + +class nsDSURIContentListener final : public nsIURIContentListener, + public nsSupportsWeakReference { + friend class nsDocShell; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURICONTENTLISTENER + + protected: + explicit nsDSURIContentListener(nsDocShell* aDocShell); + virtual ~nsDSURIContentListener(); + + void DropDocShellReference() { + mDocShell = nullptr; + mExistingJPEGRequest = nullptr; + mExistingJPEGStreamListener = nullptr; + } + + protected: + nsDocShell* mDocShell; + // Hack to handle multipart images without creating a new viewer + nsCOMPtr mExistingJPEGStreamListener; + nsCOMPtr mExistingJPEGRequest; + + // Store the parent listener in either of these depending on + // if supports weak references or not. Proper weak refs are + // preferred and encouraged! + nsWeakPtr mWeakParentContentListener; + nsIURIContentListener* mParentContentListener; +}; + +#endif /* nsDSURIContentListener_h__ */ diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp new file mode 100644 index 0000000000..57ee34cad0 --- /dev/null +++ b/docshell/base/nsDocShell.cpp @@ -0,0 +1,13877 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" + +#include + +#ifdef XP_WIN +# include +# define getpid _getpid +#else +# include // for getpid() +#endif + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Casting.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Components.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Encoding.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/HTMLEditor.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Logging.h" +#include "mozilla/MediaFeatureChange.h" +#include "mozilla/ObservedDocShell.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ScrollTypes.h" +#include "mozilla/SimpleEnumerator.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_docshell.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "mozilla/Telemetry.h" + +#include "mozilla/Unused.h" +#include "mozilla/WidgetUtils.h" + +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/ChildProcessChannelListener.h" +#include "mozilla/dom/ClientChannelHelper.h" +#include "mozilla/dom/ClientHandle.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ClientManager.h" +#include "mozilla/dom/ClientSource.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLAnchorElement.h" +#include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/PerformanceNavigation.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ServiceWorkerInterceptController.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/SessionStoreChangeListener.h" +#include "mozilla/dom/SessionStoreChild.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/ChildSHistory.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/nsHTTPSOnlyUtils.h" +#include "mozilla/dom/LoadURIOptionsBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/DocumentBinding.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentChannel.h" +#include "mozilla/net/DocumentChannelChild.h" +#include "mozilla/net/ParentChannelWrapper.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "ReferrerInfo.h" + +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsICachingChannel.h" +#include "nsICaptivePortalService.h" +#include "nsIChannel.h" +#include "nsIChannelEventSink.h" +#include "nsIClassOfService.h" +#include "nsIConsoleReportCollector.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContentViewer.h" +#include "nsIController.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "mozilla/dom/Document.h" +#include "nsHTMLDocument.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDOMWindow.h" +#include "nsIEditingSession.h" +#include "nsIEffectiveTLDService.h" +#include "nsIExternalProtocolService.h" +#include "nsIFormPOSTActionChannel.h" +#include "nsIFrame.h" +#include "nsIGlobalObject.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIDNService.h" +#include "nsIInputStreamChannel.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILayoutHistoryState.h" +#include "nsILoadInfo.h" +#include "nsILoadURIDelegate.h" +#include "nsIMultiPartChannel.h" +#include "nsINestedURI.h" +#include "nsINetworkPredictor.h" +#include "nsINode.h" +#include "nsINSSErrorsService.h" +#include "nsIObserverService.h" +#include "nsIOService.h" +#include "nsIPrincipal.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIPrompt.h" +#include "nsIPromptCollection.h" +#include "nsIPromptFactory.h" +#include "nsIPublicKeyPinningService.h" +#include "nsIReflowObserver.h" +#include "nsIScriptChannel.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollObserver.h" +#include "nsISupportsPrimitives.h" +#include "nsISecureBrowserUI.h" +#include "nsISeekableStream.h" +#include "nsISelectionDisplay.h" +#include "nsISHEntry.h" +#include "nsISiteSecurityService.h" +#include "nsISocketProvider.h" +#include "nsIStringBundle.h" +#include "nsIStructuredCloneContainer.h" +#include "nsIBrowserChild.h" +#include "nsITextToSubURI.h" +#include "nsITimedChannel.h" +#include "nsITimer.h" +#include "nsITransportSecurityInfo.h" +#include "nsIUploadChannel.h" +#include "nsIURIFixup.h" +#include "nsIURIMutator.h" +#include "nsIURILoader.h" +#include "nsIViewSourceChannel.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebBrowserChromeFocus.h" +#include "nsIWebBrowserFind.h" +#include "nsIWebProgress.h" +#include "nsIWidget.h" +#include "nsIWindowWatcher.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIX509Cert.h" +#include "nsIXULRuntime.h" + +#include "nsCommandManager.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" + +#include "IHistory.h" +#include "IUrlClassifierUITelemetry.h" + +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsContentDLF.h" +#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) +#include "nsContentSecurityManager.h" +#include "nsContentSecurityUtils.h" +#include "nsContentUtils.h" +#include "nsCURILoader.h" +#include "nsDocShellCID.h" +#include "nsDocShellEditorData.h" +#include "nsDocShellEnumerator.h" +#include "nsDocShellLoadState.h" +#include "nsDocShellLoadTypes.h" +#include "nsDOMCID.h" +#include "nsDOMNavigationTiming.h" +#include "nsDSURIContentListener.h" +#include "nsEditingSession.h" +#include "nsError.h" +#include "nsEscape.h" +#include "nsFocusManager.h" +#include "nsGlobalWindow.h" +#include "nsJSEnvironment.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsObjectLoadingContent.h" +#include "nsPingListener.h" +#include "nsPoint.h" +#include "nsQueryObject.h" +#include "nsQueryActor.h" +#include "nsRect.h" +#include "nsRefreshTimer.h" +#include "nsSandboxFlags.h" +#include "nsSHEntry.h" +#include "nsSHistory.h" +#include "nsSHEntry.h" +#include "nsStructuredCloneContainer.h" +#include "nsSubDocumentFrame.h" +#include "nsURILoader.h" +#include "nsURLHelper.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsViewSourceHandler.h" +#include "nsWebBrowserFind.h" +#include "nsWhitespaceTokenizer.h" +#include "nsWidgetsCID.h" +#include "nsXULAppAPI.h" + +#include "ThirdPartyUtil.h" +#include "GeckoProfiler.h" +#include "mozilla/NullPrincipal.h" +#include "Navigator.h" +#include "prenv.h" +#include "mozilla/ipc/URIUtils.h" +#include "sslerr.h" +#include "mozpkix/pkix.h" +#include "NSSErrorsService.h" + +#include "timeline/JavascriptTimelineMarker.h" +#include "nsDocShellTelemetryUtils.h" + +#ifdef MOZ_PLACES +# include "nsIFaviconService.h" +# include "mozIPlacesPendingOperation.h" +#endif + +#if NS_PRINT_PREVIEW +# include "nsIDocumentViewerPrint.h" +# include "nsIWebBrowserPrint.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::net; + +using mozilla::ipc::Endpoint; + +// Threshold value in ms for META refresh based redirects +#define REFRESH_REDIRECT_TIMER 15000 + +static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu"); + +#define LOGCHARSETMENU(args) \ + MOZ_LOG(gCharsetMenuLog, mozilla::LogLevel::Debug, args) + +#ifdef DEBUG +unsigned long nsDocShell::gNumberOfDocShells = 0; +static uint64_t gDocshellIDCounter = 0; + +static mozilla::LazyLogModule gDocShellLog("nsDocShell"); +static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging( + "DocShellAndDOMWindowLeak"); +#endif +static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak"); +extern mozilla::LazyLogModule gPageCacheLog; +mozilla::LazyLogModule gSHLog("SessionHistory"); +extern mozilla::LazyLogModule gSHIPBFCacheLog; + +const char kAppstringsBundleURL[] = + "chrome://global/locale/appstrings.properties"; + +static bool IsTopLevelDoc(BrowsingContext* aBrowsingContext, + nsILoadInfo* aLoadInfo) { + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(aLoadInfo); + + if (aLoadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT) { + return false; + } + + return aBrowsingContext->IsTopContent(); +} + +// True if loading for top level document loading in active tab. +static bool IsUrgentStart(BrowsingContext* aBrowsingContext, + nsILoadInfo* aLoadInfo, uint32_t aLoadType) { + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(aLoadInfo); + + if (!IsTopLevelDoc(aBrowsingContext, aLoadInfo)) { + return false; + } + + if (aLoadType & + (nsIDocShell::LOAD_CMD_NORMAL | nsIDocShell::LOAD_CMD_HISTORY)) { + return true; + } + + return aBrowsingContext->IsActive(); +} + +nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, + uint64_t aContentWindowID) + : nsDocLoader(true), + mContentWindowID(aContentWindowID), + mBrowsingContext(aBrowsingContext), + mParentCharset(nullptr), + mTreeOwner(nullptr), + mScrollbarPref(ScrollbarPreference::Auto), + mCharsetReloadState(eCharsetReloadInit), + mParentCharsetSource(0), + mFrameMargins(-1, -1), + mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome), + mPreviousEntryIndex(-1), + mLoadedEntryIndex(-1), + mBusyFlags(BUSY_FLAGS_NONE), + mAppType(nsIDocShell::APP_TYPE_UNKNOWN), + mLoadType(0), + mFailedLoadType(0), + mJSRunToCompletionDepth(0), + mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE), + mChannelToDisconnectOnPageHide(0), + mCreatingDocument(false), +#ifdef DEBUG + mInEnsureScriptEnv(false), +#endif + mInitialized(false), + mAllowSubframes(true), + mAllowMetaRedirects(true), + mAllowImages(true), + mAllowMedia(true), + mAllowDNSPrefetch(true), + mAllowWindowControl(true), + mCSSErrorReportingEnabled(false), + mAllowAuth(mItemType == typeContent), + mAllowKeywordFixup(false), + mDisableMetaRefreshWhenInactive(false), + mWindowDraggingAllowed(false), + mInFrameSwap(false), + mFiredUnloadEvent(false), + mEODForCurrentDocument(false), + mURIResultedInDocument(false), + mIsBeingDestroyed(false), + mIsExecutingOnLoadHandler(false), + mSavingOldViewer(false), + mInvisible(false), + mHasLoadedNonBlankURI(false), + mBlankTiming(false), + mTitleValidForCurrentURI(false), + mWillChangeProcess(false), + mIsNavigating(false), + mForcedAutodetection(false), + mCheckingSessionHistory(false), + mNeedToReportActiveAfterLoadingBecomesActive(false) { + // If no outer window ID was provided, generate a new one. + if (aContentWindowID == 0) { + mContentWindowID = nsContentUtils::GenerateWindowId(); + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this)); + +#ifdef DEBUG + mDocShellID = gDocshellIDCounter++; + // We're counting the number of |nsDocShells| to help find leaks + ++gNumberOfDocShells; + MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info, + ("++DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "]\n", (void*)this, + gNumberOfDocShells, getpid(), mDocShellID)); +#endif +} + +nsDocShell::~nsDocShell() { + MOZ_ASSERT(!mObserved); + + // Avoid notifying observers while we're in the dtor. + mIsBeingDestroyed = true; + + Destroy(); + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this)); + +#ifdef DEBUG + if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) { + nsAutoCString url; + if (mLastOpenedURI) { + url = mLastOpenedURI->GetSpecOrDefault(); + + // Data URLs can be very long, so truncate to avoid flooding the log. + const uint32_t maxURLLength = 1000; + if (url.Length() > maxURLLength) { + url.Truncate(maxURLLength); + } + } + + // We're counting the number of |nsDocShells| to help find leaks + --gNumberOfDocShells; + MOZ_LOG( + gDocShellAndDOMWindowLeakLogging, LogLevel::Info, + ("--DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "] [url = %s]\n", + (void*)this, gNumberOfDocShells, getpid(), mDocShellID, url.get())); + } +#endif +} + +bool nsDocShell::Initialize() { + if (mInitialized) { + // We've already been initialized. + return true; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); + mInitialized = true; + + mDisableMetaRefreshWhenInactive = + Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", + mDisableMetaRefreshWhenInactive); + + if (nsCOMPtr serv = services::GetObserverService()) { + const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_CREATE + : NS_CHROME_WEBNAVIGATION_CREATE; + serv->NotifyWhenScriptSafe(GetAsSupports(this), msg, nullptr); + } + + return true; +} + +/* static */ +already_AddRefed nsDocShell::Create( + BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) { + MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!"); + + nsresult rv; + RefPtr ds = new nsDocShell(aBrowsingContext, aContentWindowID); + + // Initialize the underlying nsDocLoader. + rv = ds->nsDocLoader::InitWithBrowsingContext(aBrowsingContext); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Create our ContentListener + ds->mContentListener = new nsDSURIContentListener(ds); + + // We enable if we're in the parent process in order to support non-e10s + // configurations. + // Note: This check is duplicated in SharedWorkerInterfaceRequestor's + // constructor. + if (XRE_IsParentProcess()) { + ds->mInterceptController = new ServiceWorkerInterceptController(); + } + + // We want to hold a strong ref to the loadgroup, so it better hold a weak + // ref to us... use an InterfaceRequestorProxy to do this. + nsCOMPtr proxy = new InterfaceRequestorProxy(ds); + ds->mLoadGroup->SetNotificationCallbacks(proxy); + + // XXX(nika): We have our BrowsingContext, so we might be able to skip this. + // It could be nice to directly set up our DocLoader tree? + rv = nsDocLoader::AddDocLoaderAsChildOfRoot(ds); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Add |ds| as a progress listener to itself. A little weird, but simpler + // than reproducing all the listener-notification logic in overrides of the + // various methods via which nsDocLoader can be notified. Note that this + // holds an nsWeakPtr to |ds|, so it's ok. + rv = ds->AddProgressListener(ds, nsIWebProgress::NOTIFY_STATE_DOCUMENT | + nsIWebProgress::NOTIFY_STATE_NETWORK | + nsIWebProgress::NOTIFY_LOCATION); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // If our BrowsingContext has private browsing enabled, update the number of + // private browsing docshells. + if (aBrowsingContext->UsePrivateBrowsing()) { + ds->NotifyPrivateBrowsingChanged(); + } + + // If our parent window is present in this process, set up our parent now. + RefPtr parentWC = aBrowsingContext->GetParentWindowContext(); + if (parentWC && parentWC->IsInProcess()) { + // If we don't have a parent element anymore, we can't finish this load! + // How'd we get here? + RefPtr parentElement = aBrowsingContext->GetEmbedderElement(); + if (!parentElement) { + MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentElement"); + return nullptr; + } + + // We have an in-process parent window, but don't have a parent nsDocShell? + // How'd we get here! + nsCOMPtr parentShell = + parentElement->OwnerDoc()->GetDocShell(); + if (!parentShell) { + MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentShell"); + return nullptr; + } + parentShell->AddChild(ds); + } + + // Make |ds| the primary DocShell for the given context. + aBrowsingContext->SetDocShell(ds); + + // Set |ds| default load flags on load group. + ds->SetLoadGroupDefaultLoadFlags(aBrowsingContext->GetDefaultLoadFlags()); + + if (XRE_IsParentProcess()) { + aBrowsingContext->Canonical()->MaybeAddAsProgressListener(ds); + } + + return ds.forget(); +} + +void nsDocShell::DestroyChildren() { + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + NS_ASSERTION(shell, "docshell has null child"); + + if (shell) { + shell->SetTreeOwner(nullptr); + } + } + + nsDocLoader::DestroyChildren(); +} + +NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsDocShell, nsDocLoader, + mScriptGlobal, mInitialClientSource, + mBrowsingContext, + mChromeEventHandler) + +NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader) +NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell) + NS_INTERFACE_MAP_ENTRY(nsIDocShell) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem) + NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIRefreshURI) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor) + NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) + NS_INTERFACE_MAP_ENTRY(nsILoadContext) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINetworkInterceptController, + mInterceptController) +NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) + +NS_IMETHODIMP +nsDocShell::GetInterface(const nsIID& aIID, void** aSink) { + MOZ_ASSERT(aSink, "null out param"); + + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsICommandManager))) { + NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); + *aSink = static_cast(mCommandManager.get()); + } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) { + *aSink = mContentListener; + } else if ((aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) || + aIID.Equals(NS_GET_IID(nsIGlobalObject)) || + aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter)) || + aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) || + aIID.Equals(NS_GET_IID(nsIDOMWindow))) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + return mScriptGlobal->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(Document)) && + NS_SUCCEEDED(EnsureContentViewer())) { + RefPtr doc = mContentViewer->GetDocument(); + doc.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + nsresult rv; + nsCOMPtr wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the an auth prompter for our window so that the parenting + // of the dialogs works as it should when using tabs. + nsIPrompt* prompt; + rv = wwatch->GetNewPrompter(mScriptGlobal, &prompt); + NS_ENSURE_SUCCESS(rv, rv); + + *aSink = prompt; + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || + aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { + return NS_SUCCEEDED(GetAuthPrompt(PROMPT_NORMAL, aIID, aSink)) + ? NS_OK + : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsISHistory))) { + // This is deprecated, you should instead directly get + // ChildSHistory from the browsing context. + MOZ_DIAGNOSTIC_ASSERT( + false, "Do not try to get a nsISHistory interface from nsIDocShell"); + return NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) { + nsresult rv = EnsureFind(); + if (NS_FAILED(rv)) { + return rv; + } + + *aSink = mFind; + NS_ADDREF((nsISupports*)*aSink); + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) { + if (PresShell* presShell = GetPresShell()) { + return presShell->QueryInterface(aIID, aSink); + } + } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) { + nsCOMPtr treeOwner; + nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_SUCCEEDED(rv) && treeOwner) { + return treeOwner->QueryInterface(aIID, aSink); + } + } else if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) { + *aSink = GetBrowserChild().take(); + return *aSink ? NS_OK : NS_ERROR_FAILURE; + } else { + return nsDocLoader::GetInterface(aIID, aSink); + } + + NS_IF_ADDREF(((nsISupports*)*aSink)); + return *aSink ? NS_OK : NS_NOINTERFACE; +} + +NS_IMETHODIMP +nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) { + // Note: this gets called fairly early (before a pageload actually starts). + // We could probably defer this even longer. + nsCOMPtr browserChild = GetBrowserChild(); + static_cast(browserChild.get()) + ->SetCancelContentJSEpoch(aEpoch); + return NS_OK; +} + +nsresult nsDocShell::CheckDisallowedJavascriptLoad( + nsDocShellLoadState* aLoadState) { + if (!net::SchemeIsJavascript(aLoadState->URI())) { + return NS_OK; + } + + if (nsCOMPtr targetPrincipal = + GetInheritedPrincipal(/* aConsiderCurrentDocument */ true)) { + if (!aLoadState->TriggeringPrincipal()->Subsumes(targetPrincipal)) { + return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; + } + return NS_OK; + } + return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) { + return LoadURI(aLoadState, aSetNavigating, false); +} + +nsresult nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating, + bool aContinueHandlingSubframeHistory) { + MOZ_ASSERT(aLoadState, "Must have a valid load state!"); + // NOTE: This comparison between what appears to be internal/external load + // flags is intentional, as it's ensuring that the caller isn't using any of + // the flags reserved for implementations by the `nsIWebNavigation` interface. + // In the future, this check may be dropped. + MOZ_ASSERT( + (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0, + "Should not have these flags set"); + MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "Targeting doesn't occur until InternalLoad"); + + if (!aLoadState->TriggeringPrincipal()) { + MOZ_ASSERT(false, "LoadURI must have a triggering principal"); + return NS_ERROR_FAILURE; + } + + MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState)); + + bool oldIsNavigating = mIsNavigating; + auto cleanupIsNavigating = + MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; }); + if (aSetNavigating) { + mIsNavigating = true; + } + + PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden; + if (aLoadState->HasLoadFlags(LOAD_FLAGS_ALLOW_POPUPS)) { + popupState = PopupBlocker::openAllowed; + // If we allow popups as part of the navigation, ensure we fake a user + // interaction, so that popups can, in fact, be allowed to open. + if (WindowContext* wc = mBrowsingContext->GetCurrentWindowContext()) { + wc->NotifyUserGestureActivation(); + } + } + + AutoPopupStatePusher statePusher(popupState); + + if (aLoadState->GetCancelContentJSEpoch().isSome()) { + SetCancelContentJSEpoch(*aLoadState->GetCancelContentJSEpoch()); + } + + // Note: we allow loads to get through here even if mFiredUnloadEvent is + // true; that case will get handled in LoadInternal or LoadHistoryEntry, + // so we pass false as the second parameter to IsNavigationAllowed. + // However, we don't allow the page to change location *in the middle of* + // firing beforeunload, so we do need to check if *beforeunload* is currently + // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP. + if (!IsNavigationAllowed(true, false)) { + return NS_OK; // JS may not handle returning of an error code + } + + nsLoadFlags defaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); + if (aLoadState->HasLoadFlags(LOAD_FLAGS_FORCE_TRR)) { + defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE; + } else if (aLoadState->HasLoadFlags(LOAD_FLAGS_DISABLE_TRR)) { + defaultLoadFlags |= nsIRequest::LOAD_TRR_DISABLED_MODE; + } + + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetDefaultLoadFlags(defaultLoadFlags)); + + if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && + mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) { + StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); + } + + // LoadType used to be set to a default value here, if no LoadInfo/LoadState + // object was passed in. That functionality has been removed as of bug + // 1492648. LoadType should now be set up by the caller at the time they + // create their nsDocShellLoadState object to pass into LoadURI. + + MOZ_LOG( + gDocShellLeakLog, LogLevel::Debug, + ("nsDocShell[%p]: loading %s with flags 0x%08x", this, + aLoadState->URI()->GetSpecOrDefault().get(), aLoadState->LoadFlags())); + + if ((!aLoadState->LoadIsFromSessionHistory() && + !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), + LOAD_FLAGS_REPLACE_HISTORY)) || + aContinueHandlingSubframeHistory) { + // This is possibly a subframe, so handle it accordingly. + // + // If history exists, it will be loaded into the aLoadState object, and the + // LoadType will be changed. + if (MaybeHandleSubframeHistory(aLoadState, + aContinueHandlingSubframeHistory)) { + // MaybeHandleSubframeHistory returns true if we need to continue loading + // asynchronously. + return NS_OK; + } + } + + if (aLoadState->LoadIsFromSessionHistory()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell[%p]: loading from session history", this)); + + if (!mozilla::SessionHistoryInParent()) { + nsCOMPtr entry = aLoadState->SHEntry(); + return LoadHistoryEntry(entry, aLoadState->LoadType(), + aLoadState->HasValidUserGestureActivation()); + } + + // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()? + return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(), + aLoadState->LoadType(), + aLoadState->HasValidUserGestureActivation()); + } + + // On history navigation via Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |location.href = ...| or + // |window.open()| + // + // LOAD_NORMAL: window.open(...) etc. + // LOAD_STOP_CONTENT: location.href = ..., location.assign(...) + if ((aLoadState->LoadType() == LOAD_NORMAL || + aLoadState->LoadType() == LOAD_STOP_CONTENT) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + BrowsingContext::Type bcType = mBrowsingContext->GetType(); + + // Set up the inheriting principal in LoadState. + nsresult rv = aLoadState->SetupInheritingPrincipal( + bcType, mBrowsingContext->OriginAttributesRef()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadState->SetupTriggeringPrincipal( + mBrowsingContext->OriginAttributesRef()); + NS_ENSURE_SUCCESS(rv, rv); + + aLoadState->CalculateLoadURIFlags(); + + MOZ_ASSERT(aLoadState->TypeHint().IsVoid(), + "Typehint should be null when calling InternalLoad from LoadURI"); + MOZ_ASSERT(aLoadState->FileName().IsVoid(), + "FileName should be null when calling InternalLoad from LoadURI"); + MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory(), + "Shouldn't be loading from an entry when calling InternalLoad " + "from LoadURI"); + + // If we have a system triggering principal, we can assume that this load was + // triggered by some UI in the browser chrome, such as the URL bar or + // bookmark bar. This should count as a user interaction for the current sh + // entry, so that the user may navigate back to the current entry, from the + // entry that is going to be added as part of this load. + nsCOMPtr triggeringPrincipal = + aLoadState->TriggeringPrincipal(); + if (triggeringPrincipal && triggeringPrincipal->IsSystemPrincipal()) { + if (mozilla::SessionHistoryInParent()) { + WindowContext* topWc = mBrowsingContext->GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true)); + } + } else { + bool oshe = false; + nsCOMPtr currentSHEntry; + GetCurrentSHEntry(getter_AddRefs(currentSHEntry), &oshe); + if (currentSHEntry) { + currentSHEntry->SetHasUserInteraction(true); + } + } + } + + rv = InternalLoad(aLoadState); + NS_ENSURE_SUCCESS(rv, rv); + + if (aLoadState->GetOriginalURIString().isSome()) { + // Save URI string in case it's needed later when + // sending to search engine service in EndPageLoad() + mOriginalUriString = *aLoadState->GetOriginalURIString(); + } + + return NS_OK; +} + +bool nsDocShell::IsLoadingFromSessionHistory() { + return mActiveEntryIsLoadingFromSessionHistory; +} + +// StopDetector is modeled similarly to OnloadBlocker; it is a rather +// dummy nsIRequest implementation which can be added to an nsILoadGroup to +// detect Cancel calls. +class StopDetector final : public nsIRequest { + public: + StopDetector() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + + bool Canceled() { return mCanceled; } + + private: + ~StopDetector() = default; + + bool mCanceled = false; +}; + +NS_IMPL_ISUPPORTS(StopDetector, nsIRequest) + +NS_IMETHODIMP +StopDetector::GetName(nsACString& aResult) { + aResult.AssignLiteral("about:stop-detector"); + return NS_OK; +} + +NS_IMETHODIMP +StopDetector::IsPending(bool* aRetVal) { + *aRetVal = true; + return NS_OK; +} + +NS_IMETHODIMP +StopDetector::GetStatus(nsresult* aStatus) { + *aStatus = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP StopDetector::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP StopDetector::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP StopDetector::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +StopDetector::Cancel(nsresult aStatus) { + mCanceled = true; + return NS_OK; +} + +NS_IMETHODIMP +StopDetector::Suspend(void) { return NS_OK; } +NS_IMETHODIMP +StopDetector::Resume(void) { return NS_OK; } + +NS_IMETHODIMP +StopDetector::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +StopDetector::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } + +NS_IMETHODIMP +StopDetector::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = nsIRequest::LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +StopDetector::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +StopDetector::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +StopDetector::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +bool nsDocShell::MaybeHandleSubframeHistory( + nsDocShellLoadState* aLoadState, bool aContinueHandlingSubframeHistory) { + // First, verify if this is a subframe. + // Note, it is ok to rely on docshell here and not browsing context since when + // an iframe is created, it has first in-process docshell. + nsCOMPtr parentAsItem; + GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr parentDS(do_QueryInterface(parentAsItem)); + + if (!parentDS || parentDS == static_cast(this)) { + if (mBrowsingContext && mBrowsingContext->IsTop()) { + // This is the root docshell. If we got here while + // executing an onLoad Handler,this load will not go + // into session history. + // XXX Why is this code in a method which deals with iframes! + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + } + } + return false; + } + + /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was + * loaded through a history mechanism, then get the SH entry for the child + * from the parent. This is done to restore frameset navigation while going + * back/forward. If the parent was loaded through any other loadType, set the + * child's loadType too accordingly, so that session history does not get + * confused. + */ + + // Get the parent's load type + uint32_t parentLoadType; + parentDS->GetLoadType(&parentLoadType); + + if (!aContinueHandlingSubframeHistory) { + if (mozilla::SessionHistoryInParent()) { + if (nsDocShell::Cast(parentDS.get())->IsLoadingFromSessionHistory() && + !GetCreatedDynamically()) { + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + nsCOMPtr loadGroup; + GetLoadGroup(getter_AddRefs(loadGroup)); + if (contentChild && loadGroup && !mCheckingSessionHistory) { + RefPtr parentDoc = parentDS->GetDocument(); + parentDoc->BlockOnload(); + RefPtr browsingContext = mBrowsingContext; + Maybe currentLoadIdentifier = + mBrowsingContext->GetCurrentLoadIdentifier(); + RefPtr loadState = aLoadState; + bool isNavigating = mIsNavigating; + RefPtr stopDetector = new StopDetector(); + loadGroup->AddRequest(stopDetector, nullptr); + // Need to set mCheckingSessionHistory so that + // GetIsAttemptingToNavigate() returns true. + mCheckingSessionHistory = true; + + auto resolve = + [currentLoadIdentifier, browsingContext, parentDoc, loadState, + isNavigating, loadGroup, stopDetector]( + mozilla::Maybe&& aResult) { + RefPtr docShell = + static_cast(browsingContext->GetDocShell()); + auto unblockParent = MakeScopeExit( + [loadGroup, stopDetector, parentDoc, docShell]() { + if (docShell) { + docShell->mCheckingSessionHistory = false; + } + loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); + parentDoc->UnblockOnload(false); + }); + + if (!docShell || !docShell->mCheckingSessionHistory) { + return; + } + + if (stopDetector->Canceled()) { + return; + } + if (currentLoadIdentifier == + browsingContext->GetCurrentLoadIdentifier() && + aResult.isSome()) { + loadState->SetLoadingSessionHistoryInfo(aResult.value()); + // This is an initial subframe load from the session + // history, index doesn't need to be updated. + loadState->SetLoadIsFromSessionHistory(0, false); + } + + // We got the results back from the parent process, call + // LoadURI again with the possibly updated data. + docShell->LoadURI(loadState, isNavigating, true); + }; + auto reject = [loadGroup, stopDetector, browsingContext, + parentDoc](mozilla::ipc::ResponseRejectReason) { + RefPtr docShell = + static_cast(browsingContext->GetDocShell()); + if (docShell) { + docShell->mCheckingSessionHistory = false; + } + // In practise reject shouldn't be called ever. + loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); + parentDoc->UnblockOnload(false); + }; + contentChild->SendGetLoadingSessionHistoryInfoFromParent( + mBrowsingContext, std::move(resolve), std::move(reject)); + return true; + } + } else { + Maybe info; + mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent( + info); + if (info.isSome()) { + aLoadState->SetLoadingSessionHistoryInfo(info.value()); + // This is an initial subframe load from the session + // history, index doesn't need to be updated. + aLoadState->SetLoadIsFromSessionHistory(0, false); + } + } + } + } else { + // Get the ShEntry for the child from the parent + nsCOMPtr currentSH; + bool oshe = false; + parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + bool dynamicallyAddedChild = GetCreatedDynamically(); + + if (!dynamicallyAddedChild && !oshe && currentSH) { + // Only use the old SHEntry, if we're sure enough that + // it wasn't originally for some other frame. + nsCOMPtr shEntry; + currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild( + mBrowsingContext->ChildOffset(), getter_AddRefs(shEntry)); + if (shEntry) { + aLoadState->SetSHEntry(shEntry); + } + } + } + } + + // Make some decisions on the child frame's loadType based on the + // parent's loadType, if the subframe hasn't loaded anything into it. + // + // In some cases privileged scripts may try to get the DOMWindow + // reference of this docshell before the loading starts, causing the + // initial about:blank content viewer being created and mCurrentURI being + // set. To handle this case we check if mCurrentURI is about:blank and + // currentSHEntry is null. + bool oshe = false; + nsCOMPtr currentChildEntry; + GetCurrentSHEntry(getter_AddRefs(currentChildEntry), &oshe); + + if (mCurrentURI && (!NS_IsAboutBlank(mCurrentURI) || currentChildEntry || + mLoadingEntry || mActiveEntry)) { + // This is a pre-existing subframe. If + // 1. The load of this frame was not originally initiated by session + // history directly (i.e. (!shEntry) condition succeeded, but it can + // still be a history load on parent which causes this frame being + // loaded), which we checked with the above assert, and + // 2. mCurrentURI is not null, nor the initial about:blank, + // it is possible that a parent's onLoadHandler or even self's + // onLoadHandler is loading a new page in this child. Check parent's and + // self's busy flag and if it is set, we don't want this onLoadHandler + // load to get in to session history. + BusyFlags parentBusy = parentDS->GetBusyFlags(); + BusyFlags selfBusy = GetBusyFlags(); + + if (parentBusy & BUSY_FLAGS_BUSY || selfBusy & BUSY_FLAGS_BUSY) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + aLoadState->ClearLoadIsFromSessionHistory(); + } + return false; + } + + // This is a newly created frame. Check for exception cases first. + // By default the subframe will inherit the parent's loadType. + if (aLoadState->LoadIsFromSessionHistory() && + (parentLoadType == LOAD_NORMAL || parentLoadType == LOAD_LINK)) { + // The parent was loaded normally. In this case, this *brand new* + // child really shouldn't have a SHEntry. If it does, it could be + // because the parent is replacing an existing frame with a new frame, + // in the onLoadHandler. We don't want this url to get into session + // history. Clear off shEntry, and set load type to + // LOAD_BYPASS_HISTORY. + bool inOnLoadHandler = false; + parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + aLoadState->ClearLoadIsFromSessionHistory(); + } + } else if (parentLoadType == LOAD_REFRESH) { + // Clear shEntry. For refresh loads, we have to load + // what comes through the pipe, not what's in history. + aLoadState->ClearLoadIsFromSessionHistory(); + } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || + (aLoadState->LoadIsFromSessionHistory() && + ((parentLoadType & LOAD_CMD_HISTORY) || + (parentLoadType == LOAD_RELOAD_NORMAL) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) || + (parentLoadType == + LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) { + // If the parent url, bypassed history or was loaded from + // history, pass on the parent's loadType to the new child + // frame too, so that the child frame will also + // avoid getting into history. + aLoadState->SetLoadType(parentLoadType); + } else if (parentLoadType == LOAD_ERROR_PAGE) { + // If the parent document is an error page, we don't + // want to update global/session history. However, + // this child frame is not an error page. + aLoadState->SetLoadType(LOAD_BYPASS_HISTORY); + } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) { + // the new frame should inherit the parent's load type so that it also + // bypasses the cache and/or proxy + aLoadState->SetLoadType(parentLoadType); + } + + return false; +} + +/* + * Reset state to a new content model within the current document and the + * document viewer. Called by the document before initiating an out of band + * document.write(). + */ +NS_IMETHODIMP +nsDocShell::PrepareForNewContentModel() { + // Clear out our form control state, because the state of controls + // in the pre-open() document should not affect the state of + // controls that are now going to be written. + SetLayoutHistoryState(nullptr); + mEODForCurrentDocument = false; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FirePageHideNotification(bool aIsUnload) { + FirePageHideNotificationInternal(aIsUnload, false); + return NS_OK; +} + +void nsDocShell::FirePageHideNotificationInternal( + bool aIsUnload, bool aSkipCheckingDynEntries) { + if (mContentViewer && !mFiredUnloadEvent) { + // Keep an explicit reference since calling PageHide could release + // mContentViewer + nsCOMPtr contentViewer(mContentViewer); + mFiredUnloadEvent = true; + + if (mTiming) { + mTiming->NotifyUnloadEventStart(); + } + + contentViewer->PageHide(aIsUnload); + + if (mTiming) { + mTiming->NotifyUnloadEventEnd(); + } + + AutoTArray, 8> kids; + uint32_t n = mChildList.Length(); + kids.SetCapacity(n); + for (uint32_t i = 0; i < n; i++) { + kids.AppendElement(do_QueryInterface(ChildAt(i))); + } + + n = kids.Length(); + for (uint32_t i = 0; i < n; ++i) { + RefPtr child = static_cast(kids[i].get()); + if (child) { + // Skip checking dynamic subframe entries in our children. + child->FirePageHideNotificationInternal(aIsUnload, true); + } + } + + // If the document is unloading, remove all dynamic subframe entries. + if (aIsUnload && !aSkipCheckingDynEntries) { + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("nsDocShell %p unloading, remove dynamic subframe entries", this)); + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + mBrowsingContext->RemoveDynEntriesFromActiveSessionHistoryEntry(); + } + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p unloading, no active entries", this)); + } else if (mOSHE) { + int32_t index = rootSH->Index(); + rootSH->LegacySHistory()->RemoveDynEntries(index, mOSHE); + } + } + } + + // Now make sure our editor, if any, is detached before we go + // any farther. + DetachEditorFromWindow(); + } +} + +void nsDocShell::ThawFreezeNonRecursive(bool aThaw) { + MOZ_ASSERT(mozilla::BFCacheInParent()); + + if (!mScriptGlobal) { + return; + } + + RefPtr inner = + mScriptGlobal->GetCurrentInnerWindowInternal(); + if (inner) { + if (aThaw) { + inner->Thaw(false); + } else { + inner->Freeze(false); + } + } +} + +void nsDocShell::FirePageHideShowNonRecursive(bool aShow) { + MOZ_ASSERT(mozilla::BFCacheInParent()); + + if (!mContentViewer) { + return; + } + + // Emulate what non-SHIP BFCache does too. In pageshow case + // add and remove a request and before that call SetCurrentURI to get + // the location change notification. + // For pagehide, set mFiredUnloadEvent to true, so that unload doesn't fire. + nsCOMPtr contentViewer(mContentViewer); + if (aShow) { + contentViewer->SetIsHidden(false); + mRefreshURIList = std::move(mBFCachedRefreshURIList); + RefreshURIFromQueue(); + mFiredUnloadEvent = false; + RefPtr doc = contentViewer->GetDocument(); + if (doc) { + doc->NotifyActivityChanged(); + RefPtr inner = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() + : nullptr; + if (mBrowsingContext->IsTop()) { + doc->NotifyPossibleTitleChange(false); + doc->SetLoadingOrRestoredFromBFCacheTimeStampToNow(); + if (inner) { + // Now that we have found the inner window of the page restored + // from the history, we have to make sure that + // performance.navigation.type is 2. + // Traditionally this type change has been done to the top level page + // only. + Performance* performance = inner->GetPerformance(); + if (performance) { + performance->GetDOMTiming()->NotifyRestoreStart(); + } + } + } + + nsCOMPtr channel = doc->GetChannel(); + if (channel) { + SetLoadType(LOAD_HISTORY); + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + SetCurrentURI(doc->GetDocumentURI(), channel, + /* aFireOnLocationChange */ true, + /* aIsInitialAboutBlank */ false, + /* aLocationFlags */ 0); + mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); + mIsRestoringDocument = false; + } + RefPtr presShell = GetPresShell(); + if (presShell) { + presShell->Thaw(false); + } + + if (inner) { + inner->FireDelayedDOMEvents(false); + } + } + } else if (!mFiredUnloadEvent) { + // XXXBFCache check again that the page can enter bfcache. + // XXXBFCache should mTiming->NotifyUnloadEventStart()/End() be called here? + + if (mRefreshURIList) { + RefreshURIToQueue(); + mBFCachedRefreshURIList = std::move(mRefreshURIList); + } else { + // If Stop was called, the list was moved to mSavedRefreshURIList after + // calling SuspendRefreshURIs, which calls RefreshURIToQueue. + mBFCachedRefreshURIList = std::move(mSavedRefreshURIList); + } + + mFiredUnloadEvent = true; + contentViewer->PageHide(false); + + RefPtr presShell = GetPresShell(); + if (presShell) { + presShell->Freeze(false); + } + } +} + +nsresult nsDocShell::Dispatch(TaskCategory aCategory, + already_AddRefed&& aRunnable) { + nsCOMPtr runnable(aRunnable); + nsCOMPtr win = GetWindow(); + if (NS_WARN_IF(!win)) { + // Window should only be unavailable after destroyed. + MOZ_ASSERT(mIsBeingDestroyed); + return NS_ERROR_FAILURE; + } + + if (win->GetDocGroup()) { + return win->GetDocGroup()->Dispatch(aCategory, runnable.forget()); + } + + return SchedulerGroup::Dispatch(aCategory, runnable.forget()); +} + +NS_IMETHODIMP +nsDocShell::DispatchLocationChangeEvent() { + return Dispatch( + TaskCategory::Other, + NewRunnableMethod("nsDocShell::FireDummyOnLocationChange", this, + &nsDocShell::FireDummyOnLocationChange)); +} + +NS_IMETHODIMP +nsDocShell::StartDelayedAutoplayMediaComponents() { + RefPtr outerWindow = GetWindow(); + if (outerWindow) { + outerWindow->ActivateMediaComponents(); + } + return NS_OK; +} + +bool nsDocShell::MaybeInitTiming() { + if (mTiming && !mBlankTiming) { + return false; + } + + bool canBeReset = false; + + if (mScriptGlobal && mBlankTiming) { + nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow(); + if (innerWin && innerWin->GetPerformance()) { + mTiming = innerWin->GetPerformance()->GetDOMTiming(); + mBlankTiming = false; + } + } + + if (!mTiming) { + mTiming = new nsDOMNavigationTiming(this); + canBeReset = true; + } + + mTiming->NotifyNavigationStart( + mBrowsingContext->IsActive() + ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + + return canBeReset; +} + +void nsDocShell::MaybeResetInitTiming(bool aReset) { + if (aReset) { + mTiming = nullptr; + } +} + +nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const { + return mTiming; +} + +nsPresContext* nsDocShell::GetEldestPresContext() { + nsIContentViewer* viewer = mContentViewer; + while (viewer) { + nsIContentViewer* prevViewer = viewer->GetPreviousViewer(); + if (!prevViewer) { + return viewer->GetPresContext(); + } + viewer = prevViewer; + } + + return nullptr; +} + +nsPresContext* nsDocShell::GetPresContext() { + if (!mContentViewer) { + return nullptr; + } + + return mContentViewer->GetPresContext(); +} + +PresShell* nsDocShell::GetPresShell() { + nsPresContext* presContext = GetPresContext(); + return presContext ? presContext->GetPresShell() : nullptr; +} + +PresShell* nsDocShell::GetEldestPresShell() { + nsPresContext* presContext = GetEldestPresContext(); + + if (presContext) { + return presContext->GetPresShell(); + } + + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) { + NS_ENSURE_ARG_POINTER(aContentViewer); + + *aContentViewer = mContentViewer; + NS_IF_ADDREF(*aContentViewer); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetOuterWindowID(uint64_t* aWindowID) { + *aWindowID = mContentWindowID; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler) { + mChromeEventHandler = aChromeEventHandler; + + if (mScriptGlobal) { + mScriptGlobal->SetChromeEventHandler(mChromeEventHandler); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChromeEventHandler(EventTarget** aChromeEventHandler) { + NS_ENSURE_ARG_POINTER(aChromeEventHandler); + RefPtr handler = mChromeEventHandler; + handler.forget(aChromeEventHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentURIForSessionStore(nsIURI* aURI) { + // Note that securityUI will set STATE_IS_INSECURE, even if + // the scheme of |aURI| is "https". + SetCurrentURI(aURI, nullptr, + /* aFireOnLocationChange */ + true, + /* aIsInitialAboutBlank */ + false, + /* aLocationFlags */ + nsIWebProgressListener::LOCATION_CHANGE_SESSION_STORE); + return NS_OK; +} + +bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, + bool aFireOnLocationChange, + bool aIsInitialAboutBlank, + uint32_t aLocationFlags) { + MOZ_ASSERT(!mIsBeingDestroyed); + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p SetCurrentURI %s\n", this, + aURI ? aURI->GetSpecOrDefault().get() : "")); + + // We don't want to send a location change when we're displaying an error + // page, and we don't want to change our idea of "current URI" either + if (mLoadType == LOAD_ERROR_PAGE) { + return false; + } + + bool uriIsEqual = false; + if (!mCurrentURI || !aURI || + NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) { + mTitleValidForCurrentURI = false; + } + + mCurrentURI = aURI; + +#ifdef DEBUG + mLastOpenedURI = aURI; +#endif + + if (!NS_IsAboutBlank(mCurrentURI)) { + mHasLoadedNonBlankURI = true; + } + + // Don't fire onLocationChange when creating a subframe's initial about:blank + // document, as this can happen when it's not safe for us to run script. + if (aIsInitialAboutBlank && !mHasLoadedNonBlankURI && + !mBrowsingContext->IsTop()) { + MOZ_ASSERT(!aRequest && aLocationFlags == 0); + return false; + } + + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + if (aFireOnLocationChange) { + FireOnLocationChange(this, aRequest, aURI, aLocationFlags); + } + return !aFireOnLocationChange; +} + +NS_IMETHODIMP +nsDocShell::GetCharset(nsACString& aCharset) { + aCharset.Truncate(); + + PresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + Document* doc = presShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->GetDocumentCharacterSet()->Name(aCharset); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ForceEncodingDetection() { + nsCOMPtr viewer; + GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_OK; + } + + Document* doc = viewer->GetDocument(); + if (!doc || doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + mForcedAutodetection = true; + + nsIURI* url = doc->GetOriginalURI(); + bool isFileURL = url && SchemeIsFile(url); + + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + auto encoding = doc->GetDocumentCharacterSet(); + // AsHTMLDocument is valid, because we called + // WillIgnoreCharsetOverride() above. + if (doc->AsHTMLDocument()->IsPlainText()) { + switch (charsetSource) { + case kCharsetFromInitialAutoDetectionASCII: + // Deliberately no final version + LOGCHARSETMENU(("TEXT:UnlabeledAscii")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledAscii); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII: + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII: + LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT:: + UnlabeledNonUtf8); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII: + LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8TLD")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT:: + UnlabeledNonUtf8TLD); + break; + case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: + case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: + LOGCHARSETMENU(("TEXT:UnlabeledUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledUtf8); + break; + case kCharsetFromChannel: + if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("TEXT:ChannelUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::ChannelUtf8); + } else { + LOGCHARSETMENU(("TEXT:ChannelNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT:: + ChannelNonUtf8); + } + break; + default: + LOGCHARSETMENU(("TEXT:Bug")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::Bug); + break; + } + } else { + switch (charsetSource) { + case kCharsetFromInitialAutoDetectionASCII: + // Deliberately no final version + LOGCHARSETMENU(("HTML:UnlabeledAscii")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledAscii); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII: + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII: + LOGCHARSETMENU(("HTML:UnlabeledNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: + UnlabeledNonUtf8); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII: + LOGCHARSETMENU(("HTML:UnlabeledNonUtf8TLD")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: + UnlabeledNonUtf8TLD); + break; + case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: + case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: + LOGCHARSETMENU(("HTML:UnlabeledUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledUtf8); + break; + case kCharsetFromChannel: + if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("HTML:ChannelUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::ChannelUtf8); + } else { + LOGCHARSETMENU(("HTML:ChannelNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: + ChannelNonUtf8); + } + break; + case kCharsetFromXmlDeclaration: + case kCharsetFromMetaTag: + if (isFileURL) { + LOGCHARSETMENU(("HTML:LocalLabeled")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::LocalLabeled); + } else if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("HTML:MetaUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::InternalUtf8); + } else { + LOGCHARSETMENU(("HTML:MetaNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: + InternalNonUtf8); + } + break; + default: + LOGCHARSETMENU(("HTML:Bug")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::Bug); + break; + } + } + return NS_OK; +} + +void nsDocShell::SetParentCharset(const Encoding*& aCharset, + int32_t aCharsetSource, + nsIPrincipal* aPrincipal) { + mParentCharset = aCharset; + mParentCharsetSource = aCharsetSource; + mParentCharsetPrincipal = aPrincipal; +} + +void nsDocShell::GetParentCharset(const Encoding*& aCharset, + int32_t* aCharsetSource, + nsIPrincipal** aPrincipal) { + aCharset = mParentCharset; + *aCharsetSource = mParentCharsetSource; + NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentBlocked(Promise** aPromise) { + MOZ_ASSERT(aPromise); + + ErrorResult rv; + RefPtr doc(GetDocument()); + RefPtr retPromise = Promise::Create(doc->GetOwnerGlobal(), rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + // Retrieve the document's content blocking events from the parent process. + RefPtr promise = + doc->GetContentBlockingEvents(); + if (promise) { + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [retPromise](const Document::GetContentBlockingEventsPromise:: + ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + bool has = aValue.ResolveValue() & + nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT; + retPromise->MaybeResolve(has); + } else { + retPromise->MaybeResolve(false); + } + }); + } else { + retPromise->MaybeResolve(false); + } + + retPromise.forget(aPromise); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowPlugins(bool* aAllowPlugins) { + NS_ENSURE_ARG_POINTER(aAllowPlugins); + + *aAllowPlugins = mBrowsingContext->GetAllowPlugins(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowPlugins(bool aAllowPlugins) { + // XXX should enable or disable a plugin host + return mBrowsingContext->SetAllowPlugins(aAllowPlugins); +} + +NS_IMETHODIMP +nsDocShell::GetCssErrorReportingEnabled(bool* aEnabled) { + MOZ_ASSERT(aEnabled); + *aEnabled = mCSSErrorReportingEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) { + mCSSErrorReportingEnabled = aEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing); +} + +void nsDocShell::NotifyPrivateBrowsingChanged() { + MOZ_ASSERT(!mIsBeingDestroyed); + + nsTObserverArray::ForwardIterator iter(mPrivacyObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (!obs) { + iter.Remove(); + } else { + obs->PrivateModeChanged(UsePrivateBrowsing()); + } + } +} + +NS_IMETHODIMP +nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { + return mBrowsingContext->SetUsePrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP +nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) { + return mBrowsingContext->SetPrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP +nsDocShell::GetHasLoadedNonBlankURI(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = mHasLoadedNonBlankURI; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs) { + NS_ENSURE_ARG_POINTER(aUseRemoteTabs); + return mBrowsingContext->GetUseRemoteTabs(aUseRemoteTabs); +} + +NS_IMETHODIMP +nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) { + return mBrowsingContext->SetRemoteTabs(aUseRemoteTabs); +} + +NS_IMETHODIMP +nsDocShell::GetUseRemoteSubframes(bool* aUseRemoteSubframes) { + NS_ENSURE_ARG_POINTER(aUseRemoteSubframes); + return mBrowsingContext->GetUseRemoteSubframes(aUseRemoteSubframes); +} + +NS_IMETHODIMP +nsDocShell::SetRemoteSubframes(bool aUseRemoteSubframes) { + return mBrowsingContext->SetRemoteSubframes(aUseRemoteSubframes); +} + +NS_IMETHODIMP +nsDocShell::AddWeakPrivacyTransitionObserver( + nsIPrivacyTransitionObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_NOT_AVAILABLE; + } + mPrivacyObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + mReflowObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver) { + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::NotifyReflowObservers(bool aInterruptible, + DOMHighResTimeStamp aStart, + DOMHighResTimeStamp aEnd) { + nsTObserverArray::ForwardIterator iter(mReflowObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (!obs) { + iter.Remove(); + } else if (aInterruptible) { + obs->ReflowInterruptible(aStart, aEnd); + } else { + obs->Reflow(aStart, aEnd); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMetaRedirects(bool* aReturn) { + NS_ENSURE_ARG_POINTER(aReturn); + + *aReturn = mAllowMetaRedirects; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowMetaRedirects(bool aValue) { + mAllowMetaRedirects = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowSubframes(bool* aAllowSubframes) { + NS_ENSURE_ARG_POINTER(aAllowSubframes); + + *aAllowSubframes = mAllowSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowSubframes(bool aAllowSubframes) { + mAllowSubframes = aAllowSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowImages(bool* aAllowImages) { + NS_ENSURE_ARG_POINTER(aAllowImages); + + *aAllowImages = mAllowImages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowImages(bool aAllowImages) { + mAllowImages = aAllowImages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMedia(bool* aAllowMedia) { + *aAllowMedia = mAllowMedia; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowMedia(bool aAllowMedia) { + mAllowMedia = aAllowMedia; + + // Mute or unmute audio contexts attached to the inner window. + if (mScriptGlobal) { + if (nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow()) { + if (aAllowMedia) { + innerWin->UnmuteAudioContexts(); + } else { + innerWin->MuteAudioContexts(); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowDNSPrefetch(bool* aAllowDNSPrefetch) { + *aAllowDNSPrefetch = mAllowDNSPrefetch; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch) { + mAllowDNSPrefetch = aAllowDNSPrefetch; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowWindowControl(bool* aAllowWindowControl) { + *aAllowWindowControl = mAllowWindowControl; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowWindowControl(bool aAllowWindowControl) { + mAllowWindowControl = aAllowWindowControl; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargeting(bool* aAllowContentRetargeting) { + *aAllowContentRetargeting = mBrowsingContext->GetAllowContentRetargeting(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) { + BrowsingContext::Transaction txn; + txn.SetAllowContentRetargeting(aAllowContentRetargeting); + txn.SetAllowContentRetargetingOnChildren(aAllowContentRetargeting); + return txn.Commit(mBrowsingContext); +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargetingOnChildren( + bool* aAllowContentRetargetingOnChildren) { + *aAllowContentRetargetingOnChildren = + mBrowsingContext->GetAllowContentRetargetingOnChildren(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargetingOnChildren( + bool aAllowContentRetargetingOnChildren) { + return mBrowsingContext->SetAllowContentRetargetingOnChildren( + aAllowContentRetargetingOnChildren); +} + +NS_IMETHODIMP +nsDocShell::GetMayEnableCharacterEncodingMenu( + bool* aMayEnableCharacterEncodingMenu) { + *aMayEnableCharacterEncodingMenu = false; + if (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + if (doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + *aMayEnableCharacterEncodingMenu = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType, + DocShellEnumeratorDirection aDirection, + nsTArray>& aResult) { + aResult.Clear(); + + nsDocShellEnumerator docShellEnum( + (aDirection == ENUMERATE_FORWARDS) + ? nsDocShellEnumerator::EnumerationDirection::Forwards + : nsDocShellEnumerator::EnumerationDirection::Backwards, + aItemType, *this); + + nsresult rv = docShellEnum.BuildDocShellArray(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAppType(AppType* aAppType) { + *aAppType = mAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAppType(AppType aAppType) { + mAppType = aAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowAuth(bool* aAllowAuth) { + *aAllowAuth = mAllowAuth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowAuth(bool aAllowAuth) { + mAllowAuth = aAllowAuth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetZoom(float* aZoom) { + NS_ENSURE_ARG_POINTER(aZoom); + *aZoom = 1.0f; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetZoom(float aZoom) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsDocShell::GetBusyFlags(BusyFlags* aBusyFlags) { + NS_ENSURE_ARG_POINTER(aBusyFlags); + + *aBusyFlags = mBusyFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, + bool* aTookFocus) { + NS_ENSURE_ARG_POINTER(aTookFocus); + + nsCOMPtr chromeFocus = do_GetInterface(mTreeOwner); + if (chromeFocus) { + if (aForward) { + *aTookFocus = + NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation)); + } else { + *aTookFocus = + NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation)); + } + } else { + *aTookFocus = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadURIDelegate(nsILoadURIDelegate** aLoadURIDelegate) { + nsCOMPtr delegate = GetLoadURIDelegate(); + delegate.forget(aLoadURIDelegate); + return NS_OK; +} + +already_AddRefed nsDocShell::GetLoadURIDelegate() { + if (nsCOMPtr result = + do_QueryActor("LoadURIDelegate", GetDocument())) { + return result.forget(); + } + + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetUseErrorPages(bool* aUseErrorPages) { + *aUseErrorPages = mBrowsingContext->GetUseErrorPages(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetUseErrorPages(bool aUseErrorPages) { + return mBrowsingContext->SetUseErrorPages(aUseErrorPages); +} + +NS_IMETHODIMP +nsDocShell::GetPreviousEntryIndex(int32_t* aPreviousEntryIndex) { + *aPreviousEntryIndex = mPreviousEntryIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadedEntryIndex(int32_t* aLoadedEntryIndex) { + *aLoadedEntryIndex = mLoadedEntryIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::HistoryPurged(int32_t aNumEntries) { + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + mPreviousEntryIndex = std::max(-1, mPreviousEntryIndex - aNumEntries); + mLoadedEntryIndex = std::max(0, mLoadedEntryIndex - aNumEntries); + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->HistoryPurged(aNumEntries); + } + } + + return NS_OK; +} + +void nsDocShell::TriggerParentCheckDocShellIsEmpty() { + if (RefPtr parent = GetInProcessParentDocshell()) { + parent->DocLoaderIsEmpty(true); + } + if (GetBrowsingContext()->IsContentSubframe() && + !GetBrowsingContext()->GetParent()->IsInProcess()) { + if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { + mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( + EmbedderElementEventType::NoEvent); + } + } +} + +nsresult nsDocShell::HistoryEntryRemoved(int32_t aIndex) { + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + if (aIndex == mPreviousEntryIndex) { + mPreviousEntryIndex = -1; + } else if (aIndex < mPreviousEntryIndex) { + --mPreviousEntryIndex; + } + if (mLoadedEntryIndex == aIndex) { + mLoadedEntryIndex = 0; + } else if (aIndex < mLoadedEntryIndex) { + --mLoadedEntryIndex; + } + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + static_cast(shell.get())->HistoryEntryRemoved(aIndex); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) { + bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers(); + if (currentValue == aValue) { + return NS_OK; + } + + if (aValue) { + MOZ_ASSERT(!TimelineConsumers::HasConsumer(this)); + TimelineConsumers::AddConsumer(this); + MOZ_ASSERT(TimelineConsumers::HasConsumer(this)); + UseEntryScriptProfiling(); + } else { + MOZ_ASSERT(TimelineConsumers::HasConsumer(this)); + TimelineConsumers::RemoveConsumer(this); + MOZ_ASSERT(!TimelineConsumers::HasConsumer(this)); + UnuseEntryScriptProfiling(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) { + *aValue = !!mObserved; + return NS_OK; +} + +nsresult nsDocShell::PopProfileTimelineMarkers( + JSContext* aCx, JS::MutableHandle aOut) { + nsTArray store; + SequenceRooter rooter(aCx, &store); + + TimelineConsumers::PopMarkers(this, aCx, store); + + if (!ToJSValue(aCx, store, aOut)) { + JS_ClearPendingException(aCx); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) { + *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetWindowDraggingAllowed(bool aValue) { + RefPtr parent = GetInProcessParentDocshell(); + if (!aValue && mItemType == typeChrome && !parent) { + // Window dragging is always allowed for top level + // chrome docshells. + return NS_ERROR_FAILURE; + } + mWindowDraggingAllowed = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetWindowDraggingAllowed(bool* aValue) { + // window dragging regions in CSS (-moz-window-drag:drag) + // can be slow. Default behavior is to only allow it for + // chrome top level windows. + RefPtr parent = GetInProcessParentDocshell(); + if (mItemType == typeChrome && !parent) { + // Top level chrome window + *aValue = true; + } else { + *aValue = mWindowDraggingAllowed; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) { + NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); + return NS_OK; +} + +nsIChannel* nsDocShell::GetCurrentDocChannel() { + if (mContentViewer) { + Document* doc = mContentViewer->GetDocument(); + if (doc) { + return doc->GetChannel(); + } + } + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + mScrollObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver) { + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +void nsDocShell::NotifyAsyncPanZoomStarted() { + nsTObserverArray::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStarted(); + } else { + iter.Remove(); + } + } +} + +void nsDocShell::NotifyAsyncPanZoomStopped() { + nsTObserverArray::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStopped(); + } else { + iter.Remove(); + } + } +} + +NS_IMETHODIMP +nsDocShell::NotifyScrollObservers() { + nsTObserverArray::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (obs) { + obs->ScrollPositionChanged(); + } else { + iter.Remove(); + } + } + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIDocShellTreeItem +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetName(nsAString& aName) { + aName = mBrowsingContext->Name(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetName(const nsAString& aName) { + return mBrowsingContext->SetName(aName); +} + +NS_IMETHODIMP +nsDocShell::NameEquals(const nsAString& aName, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mBrowsingContext->NameEquals(aName); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) { + mBrowsingContext->GetCustomUserAgent(aCustomUserAgent); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) { + if (mWillChangeProcess) { + NS_WARNING("SetCustomUserAgent: Process is changing. Ignoring set"); + return NS_ERROR_FAILURE; + } + + return mBrowsingContext->SetCustomUserAgent(aCustomUserAgent); +} + +NS_IMETHODIMP +nsDocShell::ClearCachedPlatform() { + RefPtr win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearPlatformCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ClearCachedUserAgent() { + RefPtr win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearUserAgentCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMetaViewportOverride( + MetaViewportOverride* aMetaViewportOverride) { + NS_ENSURE_ARG_POINTER(aMetaViewportOverride); + + *aMetaViewportOverride = mMetaViewportOverride; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMetaViewportOverride( + MetaViewportOverride aMetaViewportOverride) { + // We don't have a way to verify this coming from Javascript, so this check is + // still needed. + if (!(aMetaViewportOverride == META_VIEWPORT_OVERRIDE_NONE || + aMetaViewportOverride == META_VIEWPORT_OVERRIDE_ENABLED || + aMetaViewportOverride == META_VIEWPORT_OVERRIDE_DISABLED)) { + return NS_ERROR_INVALID_ARG; + } + + mMetaViewportOverride = aMetaViewportOverride; + + // Inform our presShell that it needs to re-check its need for a viewport + // override. + if (RefPtr presShell = GetPresShell()) { + presShell->MaybeRecreateMobileViewportManager(true); + } + + return NS_OK; +} + +/* virtual */ +int32_t nsDocShell::ItemType() { return mItemType; } + +NS_IMETHODIMP +nsDocShell::GetItemType(int32_t* aItemType) { + NS_ENSURE_ARG_POINTER(aItemType); + + MOZ_DIAGNOSTIC_ASSERT( + (mBrowsingContext->IsContent() ? typeContent : typeChrome) == mItemType); + *aItemType = mItemType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessParent(nsIDocShellTreeItem** aParent) { + if (!mParent) { + *aParent = nullptr; + } else { + CallQueryInterface(mParent, aParent); + } + // Note that in the case when the parent is not an nsIDocShellTreeItem we + // don't want to throw; we just want to return null. + return NS_OK; +} + +// With Fission, related nsDocShell objects may exist in a different process. In +// that case, this method will return `nullptr`, despite a parent nsDocShell +// object existing. +// +// Prefer using `BrowsingContext::Parent()`, which will succeed even if the +// parent entry is not in the current process, and handle the case where the +// parent nsDocShell is inaccessible. +already_AddRefed nsDocShell::GetInProcessParentDocshell() { + nsCOMPtr docshell = do_QueryInterface(GetAsSupports(mParent)); + return docshell.forget().downcast(); +} + +void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) { + MOZ_ASSERT(!mIsBeingDestroyed); + + // If there is an existing document then there is no need to create + // a client for a future initial about:blank document. + if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal() && + mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) { + MOZ_DIAGNOSTIC_ASSERT(mScriptGlobal->GetCurrentInnerWindowInternal() + ->GetClientInfo() + .isSome()); + MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource); + return; + } + + // Don't recreate the initial client source. We call this multiple times + // when DoChannelLoad() is called before CreateAboutBlankContentViewer. + if (mInitialClientSource) { + return; + } + + // Don't pre-allocate the client when we are sandboxed. The inherited + // principal does not take sandboxing into account. + // TODO: Refactor sandboxing principal code out so we can use it here. + if (!aPrincipal && mBrowsingContext->GetSandboxFlags()) { + return; + } + + // We cannot get inherited foreign partitioned principal here. Instead, we + // directly check which principal we want to inherit for the service worker. + nsIPrincipal* principal = + aPrincipal + ? aPrincipal + : GetInheritedPrincipal( + false, StoragePrincipalHelper:: + ShouldUsePartitionPrincipalForServiceWorker(this)); + + // Sometimes there is no principal available when we are called from + // CreateAboutBlankContentViewer. For example, sometimes the principal + // is only extracted from the load context after the document is created + // in Document::ResetToURI(). Ideally we would do something similar + // here, but for now lets just avoid the issue by not preallocating the + // client. + if (!principal) { + return; + } + + nsCOMPtr win = GetWindow(); + if (!win) { + return; + } + + mInitialClientSource = ClientManager::CreateSource( + ClientType::Window, win->EventTargetFor(TaskCategory::Other), principal); + MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource); + + // Mark the initial client as execution ready, but owned by the docshell. + // If the client is actually used this will cause ClientSource to force + // the creation of the initial about:blank by calling + // nsDocShell::GetDocument(). + mInitialClientSource->DocShellExecutionReady(this); + + // Next, check to see if the parent is controlled. + nsCOMPtr parent = GetInProcessParentDocshell(); + nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; + nsPIDOMWindowInner* parentInner = + parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; + if (!parentInner) { + return; + } + + nsCOMPtr uri; + MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns)); + + // We're done if there is no parent controller or if this docshell + // is not permitted to control for some reason. + Maybe controller(parentInner->GetController()); + if (controller.isNothing() || + !ServiceWorkerAllowedToControlWindow(principal, uri)) { + return; + } + + mInitialClientSource->InheritController(controller.ref()); +} + +Maybe nsDocShell::GetInitialClientInfo() const { + if (mInitialClientSource) { + Maybe result; + result.emplace(mInitialClientSource->Info()); + return result; + } + + nsGlobalWindowInner* innerWindow = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr; + + if (!doc || !doc->IsInitialDocument()) { + return Maybe(); + } + + return innerWindow->GetClientInfo(); +} + +nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) { + bool wasFrame = IsSubframe(); + + nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr priorityGroup = do_QueryInterface(mLoadGroup); + if (wasFrame != IsSubframe() && priorityGroup) { + priorityGroup->AdjustPriority(wasFrame ? -1 : 1); + } + + // Curse ambiguous nsISupports inheritance! + nsISupports* parent = GetAsSupports(aParent); + + // If parent is another docshell, we inherit all their flags for + // allowing plugins, scripting etc. + bool value; + nsCOMPtr parentAsDocShell(do_QueryInterface(parent)); + + if (parentAsDocShell) { + if (mAllowMetaRedirects && + NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { + SetAllowMetaRedirects(value); + } + if (mAllowSubframes && + NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { + SetAllowSubframes(value); + } + if (mAllowImages && + NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) { + SetAllowImages(value); + } + SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia); + if (mAllowWindowControl && + NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) { + SetAllowWindowControl(value); + } + if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { + value = false; + } + SetAllowDNSPrefetch(mAllowDNSPrefetch && value); + + // We don't need to inherit metaViewportOverride, because the viewport + // is only relevant for the outermost nsDocShell, not for any iframes + // like this that might be embedded within it. + } + + nsCOMPtr parentURIListener(do_GetInterface(parent)); + if (parentURIListener) { + mContentListener->SetParentContentListener(parentURIListener); + } + + return NS_OK; +} + +void nsDocShell::MaybeRestoreWindowName() { + if (!StaticPrefs::privacy_window_name_update_enabled()) { + return; + } + + // We only restore window.name for the top-level content. + if (!mBrowsingContext->IsTopContent()) { + return; + } + + nsAutoString name; + + // Following implements https://html.spec.whatwg.org/#history-traversal: + // Step 4.4. Check if the loading entry has a name. + + if (mLSHE) { + mLSHE->GetName(name); + } + + if (mLoadingEntry) { + name = mLoadingEntry->mInfo.GetName(); + } + + if (name.IsEmpty()) { + return; + } + + // Step 4.4.1. Set the name to the browsing context. + Unused << mBrowsingContext->SetName(name); + + // Step 4.4.2. Clear the name of all entries that are contiguous and + // same-origin with the loading entry. + if (mLSHE) { + nsSHistory::WalkContiguousEntries( + mLSHE, [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); + } + + if (mLoadingEntry) { + // Clear the name of the session entry in the child side. For parent side, + // the clearing will be done when we commit the history to the parent. + mLoadingEntry->mInfo.SetName(EmptyString()); + } +} + +void nsDocShell::StoreWindowNameToSHEntries() { + MOZ_ASSERT(mBrowsingContext->IsTopContent()); + + nsAutoString name; + mBrowsingContext->GetName(name); + + if (mOSHE) { + nsSHistory::WalkContiguousEntries( + mOSHE, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); + } + + if (mozilla::SessionHistoryInParent()) { + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + nsSHistory::WalkContiguousEntries( + entry, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); + } + } else { + // Ask parent process to store the name in entries. + mozilla::Unused + << ContentChild::GetSingleton() + ->SendSessionHistoryEntryStoreWindowNameInContiguousEntries( + mBrowsingContext, name); + } + } +} + +NS_IMETHODIMP +nsDocShell::GetInProcessSameTypeParent(nsIDocShellTreeItem** aParent) { + if (BrowsingContext* parentBC = mBrowsingContext->GetParent()) { + *aParent = do_AddRef(parentBC->GetDocShell()).take(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeInProcessParentIgnoreBrowserBoundaries( + nsIDocShell** aParent) { + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + nsCOMPtr parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + nsCOMPtr parentDS = do_QueryInterface(parent); + parentDS.forget(aParent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) { + NS_ENSURE_ARG_POINTER(aRootTreeItem); + + RefPtr root = this; + RefPtr parent = root->GetInProcessParentDocshell(); + while (parent) { + root = parent; + parent = root->GetInProcessParentDocshell(); + } + + root.forget(aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessSameTypeRootTreeItem( + nsIDocShellTreeItem** aRootTreeItem) { + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast(this); + + nsCOMPtr parent; + NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS( + (*aRootTreeItem)->GetInProcessSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) { + NS_ENSURE_ARG_POINTER(aTreeOwner); + + *aTreeOwner = mTreeOwner; + NS_IF_ADDREF(*aTreeOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) { + if (mIsBeingDestroyed && aTreeOwner) { + return NS_ERROR_FAILURE; + } + + // Don't automatically set the progress based on the tree owner for frames + if (!IsSubframe()) { + nsCOMPtr webProgress = + do_QueryInterface(GetAsSupports(this)); + + if (webProgress) { + nsCOMPtr oldListener = + do_QueryInterface(mTreeOwner); + nsCOMPtr newListener = + do_QueryInterface(aTreeOwner); + + if (oldListener) { + webProgress->RemoveProgressListener(oldListener); + } + + if (newListener) { + webProgress->AddProgressListener(newListener, + nsIWebProgress::NOTIFY_ALL); + } + } + } + + mTreeOwner = aTreeOwner; // Weak reference per API + + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + + if (child->ItemType() == mItemType) { + child->SetTreeOwner(aTreeOwner); + } + } + + // If we're in the content process and have had a TreeOwner set on us, extract + // our BrowserChild actor. If we've already had our BrowserChild set, assert + // that it hasn't changed. + if (mTreeOwner && XRE_IsContentProcess()) { + nsCOMPtr newBrowserChild = do_GetInterface(mTreeOwner); + MOZ_ASSERT(newBrowserChild, + "No BrowserChild actor for tree owner in Content!"); + + if (mBrowserChild) { + nsCOMPtr oldBrowserChild = + do_QueryReferent(mBrowserChild); + MOZ_RELEASE_ASSERT( + oldBrowserChild == newBrowserChild, + "Cannot change BrowserChild during nsDocShell lifetime!"); + } else { + mBrowserChild = do_GetWeakReference(newBrowserChild); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHistoryID(nsID& aID) { + aID = mBrowsingContext->GetHistoryID(); + return NS_OK; +} + +const nsID& nsDocShell::HistoryID() { return mBrowsingContext->GetHistoryID(); } + +NS_IMETHODIMP +nsDocShell::GetIsInUnload(bool* aIsInUnload) { + *aIsInUnload = mFiredUnloadEvent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessChildCount(int32_t* aChildCount) { + NS_ENSURE_ARG_POINTER(aChildCount); + *aChildCount = mChildList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddChild(nsIDocShellTreeItem* aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr childAsDocLoader = GetAsDocLoader(aChild); + NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); + + // Make sure we're not creating a loop in the docshell tree + nsDocLoader* ancestor = this; + do { + if (childAsDocLoader == ancestor) { + return NS_ERROR_ILLEGAL_VALUE; + } + ancestor = ancestor->GetParent(); + } while (ancestor); + + // Make sure to remove the child from its current parent. + nsDocLoader* childsParent = childAsDocLoader->GetParent(); + if (childsParent) { + nsresult rv = childsParent->RemoveChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to clear the treeowner in case this child is a different type + // from us. + aChild->SetTreeOwner(nullptr); + + nsresult res = AddChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(res, res); + NS_ASSERTION(!mChildList.IsEmpty(), + "child list must not be empty after a successful add"); + + /* Set the child's global history if the parent has one */ + if (mBrowsingContext->GetUseGlobalHistory()) { + // childDocShell->SetUseGlobalHistory(true); + // this should be set through BC inherit + MOZ_ASSERT(aChild->GetBrowsingContext()->GetUseGlobalHistory()); + } + + if (aChild->ItemType() != mItemType) { + return NS_OK; + } + + aChild->SetTreeOwner(mTreeOwner); + + nsCOMPtr childAsDocShell(do_QueryInterface(aChild)); + if (!childAsDocShell) { + return NS_OK; + } + + // charset, style-disabling, and zoom will be inherited in SetupNewViewer() + + // Now take this document's charset and set the child's parentCharset field + // to it. We'll later use that field, in the loading process, for the + // charset choosing algorithm. + // If we fail, at any point, we just return NS_OK. + // This code has some performance impact. But this will be reduced when + // the current charset will finally be stored as an Atom, avoiding the + // alias resolution extra look-up. + + // we are NOT going to propagate the charset is this Chrome's docshell + if (mItemType == nsIDocShellTreeItem::typeChrome) { + return NS_OK; + } + + // get the parent's current charset + if (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + + const Encoding* parentCS = doc->GetDocumentCharacterSet(); + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + // set the child's parentCharset + childAsDocShell->SetParentCharset(parentCS, charsetSource, + doc->NodePrincipal()); + + // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n", + // NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr childAsDocLoader = GetAsDocLoader(aChild); + NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); + + nsresult rv = RemoveChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(rv, rv); + + aChild->SetTreeOwner(nullptr); + + return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader); +} + +NS_IMETHODIMP +nsDocShell::GetInProcessChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr child = GetInProcessChildAt(aIndex); + NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED); + + child.forget(aChild); + + return NS_OK; +} + +nsDocShell* nsDocShell::GetInProcessChildAt(int32_t aIndex) { +#ifdef DEBUG + if (aIndex < 0) { + NS_WARNING("Negative index passed to GetChildAt"); + } else if (static_cast(aIndex) >= mChildList.Length()) { + NS_WARNING("Too large an index passed to GetChildAt"); + } +#endif + + nsIDocumentLoader* child = ChildAt(aIndex); + + // child may be nullptr here. + return static_cast(child); +} + +nsresult nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef, + nsISHEntry* aNewEntry, + int32_t aChildOffset, uint32_t aLoadType, + bool aCloneChildren) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + nsresult rv = NS_OK; + + if (mLSHE && aLoadType != LOAD_PUSHSTATE) { + /* You get here if you are currently building a + * hierarchy ie.,you just visited a frameset page + */ + if (NS_FAILED(mLSHE->ReplaceChild(aNewEntry))) { + rv = mLSHE->AddChild(aNewEntry, aChildOffset); + } + } else if (!aCloneRef) { + /* This is an initial load in some subframe. Just append it if we can */ + if (mOSHE) { + rv = mOSHE->AddChild(aNewEntry, aChildOffset, UseRemoteSubframes()); + } + } else { + RefPtr shistory = GetRootSessionHistory(); + if (shistory) { + rv = shistory->LegacySHistory()->AddChildSHEntryHelper( + aCloneRef, aNewEntry, mBrowsingContext->Top(), aCloneChildren); + } + } + return rv; +} + +nsresult nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, + int32_t aChildOffset, + bool aCloneChildren) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + /* You will get here when you are in a subframe and + * a new url has been loaded on you. + * The mOSHE in this subframe will be the previous url's + * mOSHE. This mOSHE will be used as the identification + * for this subframe in the CloneAndReplace function. + */ + + // In this case, we will end up calling AddEntry, which increases the + // current index by 1 + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + mPreviousEntryIndex = rootSH->Index(); + } + + nsresult rv; + // XXX(farre): this is not Fission safe, expect errors. This never + // get's executed once session history in the parent is enabled. + nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent), &rv); + NS_WARNING_ASSERTION( + parent || !UseRemoteSubframes(), + "Failed to add child session history entry! This will be resolved once " + "session history in the parent is enabled."); + if (parent) { + rv = nsDocShell::Cast(parent)->AddChildSHEntry( + mOSHE, aNewEntry, aChildOffset, mLoadType, aCloneChildren); + } + + if (rootSH) { + mLoadedEntryIndex = rootSH->Index(); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) { + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, + mLoadedEntryIndex)); + } + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) { + *aOSHE = false; + *aEntry = nullptr; + if (mLSHE) { + NS_ADDREF(*aEntry = mLSHE); + } else if (mOSHE) { + NS_ADDREF(*aEntry = mOSHE); + *aOSHE = true; + } + return NS_OK; +} + +NS_IMETHODIMP nsDocShell::SynchronizeLayoutHistoryState() { + if (mActiveEntry && mActiveEntry->GetLayoutHistoryState() && + mBrowsingContext) { + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + contentChild->SendSynchronizeLayoutHistoryState( + mBrowsingContext, mActiveEntry->GetLayoutHistoryState()); + } + } else { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetLayoutHistoryState(mActiveEntry->GetLayoutHistoryState()); + } + } + if (mLoadingEntry && + mLoadingEntry->mInfo.SharedId() == mActiveEntry->SharedId()) { + mLoadingEntry->mInfo.SetLayoutHistoryState( + mActiveEntry->GetLayoutHistoryState()); + } + } + + return NS_OK; +} + +void nsDocShell::SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags) { + if (mLoadGroup) { + mLoadGroup->SetDefaultLoadFlags(aLoadFlags); + } else { + NS_WARNING( + "nsDocShell::SetLoadGroupDefaultLoadFlags has no loadGroup to " + "propagate the mode to"); + } +} + +nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() { + NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr); + return mScriptGlobal; +} + +Document* nsDocShell::GetDocument() { + NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr); + return mContentViewer->GetDocument(); +} + +Document* nsDocShell::GetExtantDocument() { + return mContentViewer ? mContentViewer->GetDocument() : nullptr; +} + +nsPIDOMWindowOuter* nsDocShell::GetWindow() { + if (NS_FAILED(EnsureScriptEnvironment())) { + return nullptr; + } + return mScriptGlobal; +} + +NS_IMETHODIMP +nsDocShell::GetDomWindow(mozIDOMWindowProxy** aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + + nsresult rv = EnsureScriptEnvironment(); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr window = mScriptGlobal; + window.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { + RefPtr mm; + if (RefPtr browserChild = BrowserChild::GetFrom(this)) { + mm = browserChild->GetMessageManager(); + } else if (nsPIDOMWindowOuter* win = GetWindow()) { + mm = win->GetMessageManager(); + } + mm.forget(aMessageManager); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsNavigating(bool* aOut) { + *aOut = mIsNavigating; + return NS_OK; +} + +void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + RefPtr rootSH = GetRootSessionHistory(); + if (!rootSH || !aEntry) { + return; + } + + rootSH->LegacySHistory()->RemoveFrameEntries(aEntry); +} + +//------------------------------------- +//-- Helper Method for Print discovery +//------------------------------------- +bool nsDocShell::NavigationBlockedByPrinting(bool aDisplayErrorDialog) { + if (!mBrowsingContext->Top()->GetIsPrinting()) { + return false; + } + if (aDisplayErrorDialog) { + DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr); + } + return true; +} + +bool nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog, + bool aCheckIfUnloadFired) { + bool isAllowed = !NavigationBlockedByPrinting(aDisplayPrintErrorDialog) && + (!aCheckIfUnloadFired || !mFiredUnloadEvent); + if (!isAllowed) { + return false; + } + if (!mContentViewer) { + return true; + } + bool firingBeforeUnload; + mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload); + return !firingBeforeUnload; +} + +//***************************************************************************** +// nsDocShell::nsIWebNavigation +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetCanGoBack(bool* aCanGoBack) { + *aCanGoBack = false; + if (!IsNavigationAllowed(false)) { + return NS_OK; // JS may not handle returning of an error code + } + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + *aCanGoBack = rootSH->CanGo(-1); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("nsDocShell %p CanGoBack()->%d", this, *aCanGoBack)); + + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetCanGoForward(bool* aCanGoForward) { + *aCanGoForward = false; + if (!IsNavigationAllowed(false)) { + return NS_OK; // JS may not handle returning of an error code + } + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + *aCanGoForward = rootSH->CanGo(1); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("nsDocShell %p CanGoForward()->%d", this, *aCanGoForward)); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GoBack(bool aRequireUserInteraction, bool aUserActivation) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + ErrorResult rv; + rootSH->Go(-1, aRequireUserInteraction, aUserActivation, rv); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +nsDocShell::GoForward(bool aRequireUserInteraction, bool aUserActivation) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + ErrorResult rv; + rootSH->Go(1, aRequireUserInteraction, aUserActivation, rv); + return rv.StealNSResult(); +} + +// XXX(nika): We may want to stop exposing this API in the child process? Going +// to a specific index from multiple different processes could definitely race. +NS_IMETHODIMP +nsDocShell::GotoIndex(int32_t aIndex, bool aUserActivation) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + + ErrorResult rv; + rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, aUserActivation, + rv); + return rv.StealNSResult(); +} + +nsresult nsDocShell::LoadURI(nsIURI* aURI, + const LoadURIOptions& aLoadURIOptions) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + RefPtr loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState)); + MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI); + if (NS_FAILED(rv) || !loadState) { + return NS_ERROR_FAILURE; + } + + return LoadURI(loadState, true); +} + +NS_IMETHODIMP +nsDocShell::LoadURIFromScript(nsIURI* aURI, + JS::Handle aLoadURIOptions, + JSContext* aCx) { + // generate dictionary for aLoadURIOptions and forward call + LoadURIOptions loadURIOptions; + if (!loadURIOptions.Init(aCx, aLoadURIOptions)) { + return NS_ERROR_INVALID_ARG; + } + return LoadURI(aURI, loadURIOptions); +} + +nsresult nsDocShell::FixupAndLoadURIString( + const nsAString& aURIString, const LoadURIOptions& aLoadURIOptions) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + RefPtr loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + mBrowsingContext, aURIString, aLoadURIOptions, getter_AddRefs(loadState)); + + uint32_t loadFlags = aLoadURIOptions.mLoadFlags; + if (NS_ERROR_MALFORMED_URI == rv) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Creating an active entry on nsDocShell %p to %s (because " + "we're showing an error page)", + this, NS_ConvertUTF16toUTF8(aURIString).get())); + + // We need to store a session history entry. We don't have a valid URI, so + // we use about:blank instead. + nsCOMPtr uri; + MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns)); + nsCOMPtr triggeringPrincipal; + if (aLoadURIOptions.mTriggeringPrincipal) { + triggeringPrincipal = aLoadURIOptions.mTriggeringPrincipal; + } else { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + if (mozilla::SessionHistoryInParent()) { + mActiveEntry = MakeUnique( + uri, triggeringPrincipal, nullptr, nullptr, nullptr, + nsLiteralCString("text/html")); + mBrowsingContext->SetActiveSessionHistoryEntry( + Nothing(), mActiveEntry.get(), MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags), + /* aUpdatedCacheKey = */ 0); + } + if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURIString).get(), + nullptr) && + (loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + if (NS_FAILED(rv) || !loadState) { + return NS_ERROR_FAILURE; + } + + return LoadURI(loadState, true); +} + +NS_IMETHODIMP +nsDocShell::FixupAndLoadURIStringFromScript( + const nsAString& aURIString, JS::Handle aLoadURIOptions, + JSContext* aCx) { + // generate dictionary for aLoadURIOptions and forward call + LoadURIOptions loadURIOptions; + if (!loadURIOptions.Init(aCx, aLoadURIOptions)) { + return NS_ERROR_INVALID_ARG; + } + return FixupAndLoadURIString(aURIString, loadURIOptions); +} + +void nsDocShell::UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent) { + // If we're not in a content frame, or are at a BrowsingContext tree boundary, + // such as the content-chrome boundary, don't fire the error event. + if (mBrowsingContext->IsTopContent() || mBrowsingContext->IsChrome()) { + return; + } + + // If embedder is same-process, then unblocking the load event is already + // handled by nsDocLoader. Fire the error event on our embedder element if + // requested. + // + // XXX: Bug 1440212 is looking into potentially changing this behaviour to act + // more like the remote case when in-process. + RefPtr element = mBrowsingContext->GetEmbedderElement(); + if (element) { + if (aFireFrameErrorEvent) { + if (RefPtr flo = do_QueryObject(element)) { + if (RefPtr fl = flo->GetFrameLoader()) { + fl->FireErrorEvent(); + } + } + } + return; + } + + // If we have a cross-process parent document, we must notify it that we no + // longer block its load event. This is necessary for OOP sub-documents + // because error documents do not result in a call to + // SendMaybeFireEmbedderLoadEvents via any of the normal call paths. + // (Obviously, we must do this before any of the returns below.) + RefPtr browserChild = BrowserChild::GetFrom(this); + if (browserChild) { + mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( + aFireFrameErrorEvent ? EmbedderElementEventType::ErrorEvent + : EmbedderElementEventType::NoEvent); + } +} + +NS_IMETHODIMP +nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, + const char16_t* aURL, nsIChannel* aFailedChannel, + bool* aDisplayedErrorPage) { + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p DisplayLoadError %s\n", this, + aURI ? aURI->GetSpecOrDefault().get() : "")); + + *aDisplayedErrorPage = false; + // Get prompt and string bundle services + nsCOMPtr prompter; + nsCOMPtr stringBundle; + GetPromptAndStringBundle(getter_AddRefs(prompter), + getter_AddRefs(stringBundle)); + + NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE); + + const char* error = nullptr; + // The key used to select the appropriate error message from the properties + // file. + const char* errorDescriptionID = nullptr; + AutoTArray formatStrs; + bool addHostPort = false; + bool isBadStsCertError = false; + nsresult rv = NS_OK; + nsAutoString messageStr; + nsAutoCString cssClass; + nsAutoCString errorPage; + + errorPage.AssignLiteral("neterror"); + + // Turn the error code into a human readable error message. + if (NS_ERROR_UNKNOWN_PROTOCOL == aError) { + NS_ENSURE_ARG_POINTER(aURI); + + // Extract the schemes into a comma delimited list. + nsAutoCString scheme; + aURI->GetScheme(scheme); + CopyASCIItoUTF16(scheme, *formatStrs.AppendElement()); + nsCOMPtr nestedURI = do_QueryInterface(aURI); + while (nestedURI) { + nsCOMPtr tempURI; + nsresult rv2; + rv2 = nestedURI->GetInnerURI(getter_AddRefs(tempURI)); + if (NS_SUCCEEDED(rv2) && tempURI) { + tempURI->GetScheme(scheme); + formatStrs[0].AppendLiteral(", "); + AppendASCIItoUTF16(scheme, formatStrs[0]); + } + nestedURI = do_QueryInterface(tempURI); + } + error = "unknownProtocolFound"; + } else if (NS_ERROR_FILE_NOT_FOUND == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error = "fileNotFound"; + } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error = "fileAccessDenied"; + } else if (NS_ERROR_UNKNOWN_HOST == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + nsCOMPtr innermostURI = NS_GetInnermostURI(aURI); + innermostURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + errorDescriptionID = "dnsNotFound2"; + error = "dnsNotFound"; + } else if (NS_ERROR_CONNECTION_REFUSED == aError || + NS_ERROR_PROXY_BAD_GATEWAY == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error = "connectionFailure"; + } else if (NS_ERROR_NET_INTERRUPT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error = "netInterrupt"; + } else if (NS_ERROR_NET_TIMEOUT == aError || + NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError || + NS_ERROR_NET_TIMEOUT_EXTERNAL == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + error = "netTimeout"; + } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError || + NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError || + NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) { + // CSP error + cssClass.AssignLiteral("neterror"); + error = "cspBlocked"; + } else if (NS_ERROR_XFO_VIOLATION == aError) { + // XFO error + cssClass.AssignLiteral("neterror"); + error = "xfoBlocked"; + } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { + nsCOMPtr nsserr = + do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); + + uint32_t errorClass; + if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) { + errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL; + } + + nsCOMPtr tsi; + if (aFailedChannel) { + aFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); + } + if (tsi) { + uint32_t securityState; + tsi->GetSecurityState(&securityState); + if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) { + error = "sslv3Used"; + addHostPort = true; + } else if (securityState & + nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) { + error = "weakCryptoUsed"; + addHostPort = true; + } + } else { + // No channel, let's obtain the generic error message + if (nsserr) { + nsserr->GetErrorMessage(aError, messageStr); + } + } + // We don't have a message string here anymore but DisplayLoadError + // requires a non-empty messageStr. + messageStr.Truncate(); + messageStr.AssignLiteral(u" "); + if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { + error = "nssBadCert"; + + // If this is an HTTP Strict Transport Security host or a pinned host + // and the certificate is bad, don't allow overrides (RFC 6797 section + // 12.1). + bool isStsHost = false; + bool isPinnedHost = false; + OriginAttributes attrsForHSTS; + if (aFailedChannel) { + StoragePrincipalHelper::GetOriginAttributesForHSTS(aFailedChannel, + attrsForHSTS); + } else { + attrsForHSTS = GetOriginAttributes(); + } + + if (XRE_IsParentProcess()) { + nsCOMPtr sss = + do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(aURI, attrsForHSTS, &isStsHost); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + cc->SendIsSecureURI(aURI, attrsForHSTS, &isStsHost); + } + nsCOMPtr pkps = + do_GetService(NS_PKPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = pkps->HostHasPins(aURI, &isPinnedHost); + + if (Preferences::GetBool("browser.xul.error_pages.expert_bad_cert", + false)) { + cssClass.AssignLiteral("expertBadCert"); + } + + // HSTS/pinning takes precedence over the expert bad cert pref. We + // never want to show the "Add Exception" button for these sites. + // In the future we should differentiate between an HSTS host and a + // pinned host and display a more informative message to the user. + if (isStsHost || isPinnedHost) { + isBadStsCertError = true; + cssClass.AssignLiteral("badStsCert"); + } + + // See if an alternate cert error page is registered + nsAutoCString alternateErrorPage; + nsresult rv = Preferences::GetCString( + "security.alternate_certificate_error_page", alternateErrorPage); + if (NS_SUCCEEDED(rv)) { + errorPage.Assign(alternateErrorPage); + } + } else { + error = "nssFailure2"; + } + } else if (NS_ERROR_PHISHING_URI == aError || + NS_ERROR_MALWARE_URI == aError || + NS_ERROR_UNWANTED_URI == aError || + NS_ERROR_HARMFUL_URI == aError) { + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + + // Malware and phishing detectors may want to use an alternate error + // page, but if the pref's not set, we'll fall back on the standard page + nsAutoCString alternateErrorPage; + nsresult rv = Preferences::GetCString("urlclassifier.alternate_error_page", + alternateErrorPage); + if (NS_SUCCEEDED(rv)) { + errorPage.Assign(alternateErrorPage); + } + + if (NS_ERROR_PHISHING_URI == aError) { + error = "deceptiveBlocked"; + } else if (NS_ERROR_MALWARE_URI == aError) { + error = "malwareBlocked"; + } else if (NS_ERROR_UNWANTED_URI == aError) { + error = "unwantedBlocked"; + } else if (NS_ERROR_HARMFUL_URI == aError) { + error = "harmfulBlocked"; + } + + cssClass.AssignLiteral("blacklist"); + } else if (NS_ERROR_CONTENT_CRASHED == aError) { + errorPage.AssignLiteral("tabcrashed"); + error = "tabcrashed"; + + RefPtr handler = mChromeEventHandler; + if (handler) { + nsCOMPtr element = do_QueryInterface(handler); + element->GetAttribute(u"crashedPageTitle"_ns, messageStr); + } + + // DisplayLoadError requires a non-empty messageStr to proceed and call + // LoadErrorPage. If the page doesn't have a title, we will use a blank + // space which will be trimmed and thus treated as empty by the front-end. + if (messageStr.IsEmpty()) { + messageStr.AssignLiteral(u" "); + } + } else if (NS_ERROR_FRAME_CRASHED == aError) { + errorPage.AssignLiteral("framecrashed"); + error = "framecrashed"; + messageStr.AssignLiteral(u" "); + } else if (NS_ERROR_BUILDID_MISMATCH == aError) { + errorPage.AssignLiteral("restartrequired"); + error = "restartrequired"; + + // DisplayLoadError requires a non-empty messageStr to proceed and call + // LoadErrorPage. If the page doesn't have a title, we will use a blank + // space which will be trimmed and thus treated as empty by the front-end. + if (messageStr.IsEmpty()) { + messageStr.AssignLiteral(u" "); + } + } else { + // Errors requiring simple formatting + switch (aError) { + case NS_ERROR_MALFORMED_URI: + // URI is malformed + error = "malformedURI"; + errorDescriptionID = "malformedURI2"; + break; + case NS_ERROR_REDIRECT_LOOP: + // Doc failed to load because the server generated too many redirects + error = "redirectLoop"; + break; + case NS_ERROR_UNKNOWN_SOCKET_TYPE: + // Doc failed to load because PSM is not installed + error = "unknownSocketType"; + break; + case NS_ERROR_NET_RESET: + // Doc failed to load because the server kept reseting the connection + // before we could read any data from it + error = "netReset"; + break; + case NS_ERROR_DOCUMENT_NOT_CACHED: + // Doc failed to load because the cache does not contain a copy of + // the document. + error = "notCached"; + break; + case NS_ERROR_OFFLINE: + // Doc failed to load because we are offline. + error = "netOffline"; + break; + case NS_ERROR_DOCUMENT_IS_PRINTMODE: + // Doc navigation attempted while Printing or Print Preview + error = "isprinting"; + break; + case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: + // Port blocked for security reasons + addHostPort = true; + error = "deniedPortAccess"; + break; + case NS_ERROR_UNKNOWN_PROXY_HOST: + // Proxy hostname could not be resolved. + error = "proxyResolveFailure"; + break; + case NS_ERROR_PROXY_CONNECTION_REFUSED: + case NS_ERROR_PROXY_FORBIDDEN: + case NS_ERROR_PROXY_NOT_IMPLEMENTED: + case NS_ERROR_PROXY_AUTHENTICATION_FAILED: + case NS_ERROR_PROXY_TOO_MANY_REQUESTS: + // Proxy connection was refused. + error = "proxyConnectFailure"; + break; + case NS_ERROR_INVALID_CONTENT_ENCODING: + // Bad Content Encoding. + error = "contentEncodingError"; + break; + case NS_ERROR_UNSAFE_CONTENT_TYPE: + // Channel refused to load from an unrecognized content type. + error = "unsafeContentType"; + break; + case NS_ERROR_CORRUPTED_CONTENT: + // Broken Content Detected. e.g. Content-MD5 check failure. + error = "corruptedContentErrorv2"; + break; + case NS_ERROR_INTERCEPTION_FAILED: + // ServiceWorker intercepted request, but something went wrong. + error = "corruptedContentErrorv2"; + break; + case NS_ERROR_NET_INADEQUATE_SECURITY: + // Server negotiated bad TLS for HTTP/2. + error = "inadequateSecurityError"; + addHostPort = true; + break; + case NS_ERROR_BLOCKED_BY_POLICY: + case NS_ERROR_DOM_COOP_FAILED: + case NS_ERROR_DOM_COEP_FAILED: + // Page blocked by policy + error = "blockedByPolicy"; + break; + case NS_ERROR_NET_HTTP2_SENT_GOAWAY: + case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR: + // HTTP/2 or HTTP/3 stack detected a protocol error + error = "networkProtocolError"; + break; + + default: + break; + } + } + + nsresult delegateErrorCode = aError; + // If the HTTPS-Only Mode upgraded this request and the upgrade might have + // caused this error, we replace the error-page with about:httpsonlyerror + if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError)) { + errorPage.AssignLiteral("httpsonlyerror"); + delegateErrorCode = NS_ERROR_HTTPS_ONLY; + } else if (isBadStsCertError) { + delegateErrorCode = NS_ERROR_BAD_HSTS_CERT; + } + + if (nsCOMPtr loadURIDelegate = GetLoadURIDelegate()) { + nsCOMPtr errorPageURI; + rv = loadURIDelegate->HandleLoadError( + aURI, delegateErrorCode, NS_ERROR_GET_MODULE(delegateErrorCode), + getter_AddRefs(errorPageURI)); + // If the docshell is going away there's no point in showing an error page. + if (NS_FAILED(rv) || mIsBeingDestroyed) { + *aDisplayedErrorPage = false; + return NS_OK; + } + + if (errorPageURI) { + *aDisplayedErrorPage = + NS_SUCCEEDED(LoadErrorPage(errorPageURI, aURI, aFailedChannel)); + return NS_OK; + } + } + + // Test if the error should be displayed + if (!error) { + return NS_OK; + } + + if (!errorDescriptionID) { + errorDescriptionID = error; + } + + Telemetry::AccumulateCategoricalKeyed( + IsSubframe() ? "frame"_ns : "top"_ns, + mozilla::dom::LoadErrorToTelemetryLabel(aError)); + + // Test if the error needs to be formatted + if (!messageStr.IsEmpty()) { + // already obtained message + } else { + if (addHostPort) { + // Build up the host:port string. + nsAutoCString hostport; + if (aURI) { + aURI->GetHostPort(hostport); + } else { + hostport.Assign('?'); + } + CopyUTF8toUTF16(hostport, *formatStrs.AppendElement()); + } + + nsAutoCString spec; + rv = NS_ERROR_NOT_AVAILABLE; + auto& nextFormatStr = *formatStrs.AppendElement(); + if (aURI) { + // displaying "file://" is aesthetically unpleasing and could even be + // confusing to the user + if (SchemeIsFile(aURI)) { + aURI->GetPathQueryRef(spec); + } else { + aURI->GetSpec(spec); + } + + nsCOMPtr textToSubURI( + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = textToSubURI->UnEscapeURIForUI(spec, nextFormatStr); + } + } else { + spec.Assign('?'); + } + if (NS_FAILED(rv)) { + CopyUTF8toUTF16(spec, nextFormatStr); + } + rv = NS_OK; + + nsAutoString str; + rv = + stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str); + NS_ENSURE_SUCCESS(rv, rv); + messageStr.Assign(str); + } + + // Display the error as a page or an alert prompt + NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE); + + if ((NS_ERROR_NET_INTERRUPT == aError || NS_ERROR_NET_RESET == aError) && + SchemeIsHTTPS(aURI)) { + // Maybe TLS intolerant. Treat this as an SSL error. + error = "nssFailure2"; + } + + if (mBrowsingContext->GetUseErrorPages()) { + // Display an error page + nsresult loadedPage = + LoadErrorPage(aURI, aURL, errorPage.get(), error, messageStr.get(), + cssClass.get(), aFailedChannel); + *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage); + } else { + // The prompter reqires that our private window has a document (or it + // asserts). Satisfy that assertion now since GetDoc will force + // creation of one if it hasn't already been created. + if (mScriptGlobal) { + Unused << mScriptGlobal->GetDoc(); + } + + // Display a message box + prompter->Alert(nullptr, messageStr.get()); + } + + return NS_OK; +} + +#define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride" + +nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, + const char* aErrorPage, + const char* aErrorType, + const char16_t* aDescription, + const char* aCSSClass, + nsIChannel* aFailedChannel) { + if (mIsBeingDestroyed) { + return NS_ERROR_NOT_AVAILABLE; + } + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aFailedChannel) { + aFailedChannel->GetName(chanName); + } else { + chanName.AssignLiteral(""); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", + this, aURI ? aURI->GetSpecOrDefault().get() : "", + NS_ConvertUTF16toUTF8(aURL).get(), chanName.get())); + } +#endif + + nsAutoCString url; + if (aURI) { + nsresult rv = aURI->GetSpec(url); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aURL) { + CopyUTF16toUTF8(MakeStringSpan(aURL), url); + } else { + return NS_ERROR_INVALID_POINTER; + } + + // Create a URL to pass all the error information through to the page. + +#undef SAFE_ESCAPE +#define SAFE_ESCAPE(output, input, params) \ + if (NS_WARN_IF(!NS_Escape(input, output, params))) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } + + nsCString escapedUrl, escapedError, escapedDescription, escapedCSSClass; + SAFE_ESCAPE(escapedUrl, url, url_Path); + SAFE_ESCAPE(escapedError, nsDependentCString(aErrorType), url_Path); + SAFE_ESCAPE(escapedDescription, NS_ConvertUTF16toUTF8(aDescription), + url_Path); + if (aCSSClass) { + nsCString cssClass(aCSSClass); + SAFE_ESCAPE(escapedCSSClass, cssClass, url_Path); + } + nsCString errorPageUrl("about:"); + errorPageUrl.AppendASCII(aErrorPage); + errorPageUrl.AppendLiteral("?e="); + + errorPageUrl.AppendASCII(escapedError.get()); + errorPageUrl.AppendLiteral("&u="); + errorPageUrl.AppendASCII(escapedUrl.get()); + if ((strcmp(aErrorPage, "blocked") == 0) && + Preferences::GetBool(PREF_SAFEBROWSING_ALLOWOVERRIDE, true)) { + errorPageUrl.AppendLiteral("&o=1"); + } + if (!escapedCSSClass.IsEmpty()) { + errorPageUrl.AppendLiteral("&s="); + errorPageUrl.AppendASCII(escapedCSSClass.get()); + } + errorPageUrl.AppendLiteral("&c=UTF-8"); + + nsCOMPtr cps = do_GetService(NS_CAPTIVEPORTAL_CID); + int32_t cpsState; + if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) && + cpsState == nsICaptivePortalService::LOCKED_PORTAL) { + errorPageUrl.AppendLiteral("&captive=true"); + } + + errorPageUrl.AppendLiteral("&d="); + errorPageUrl.AppendASCII(escapedDescription.get()); + + nsCOMPtr errorPageURI; + nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + return LoadErrorPage(errorPageURI, aURI, aFailedChannel); +} + +nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI, + nsIChannel* aFailedChannel) { + mFailedChannel = aFailedChannel; + mFailedURI = aFailedURI; + mFailedLoadType = mLoadType; + + if (mLSHE) { + // Abandon mLSHE's BFCache entry and create a new one. This way, if + // we go back or forward to another SHEntry with the same doc + // identifier, the error page won't persist. + mLSHE->AbandonBFCacheEntry(); + } + + RefPtr loadState = new nsDocShellLoadState(aErrorURI); + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + if (mBrowsingContext) { + loadState->SetTriggeringSandboxFlags(mBrowsingContext->GetSandboxFlags()); + } + loadState->SetLoadType(LOAD_ERROR_PAGE); + loadState->SetFirstParty(true); + loadState->SetSourceBrowsingContext(mBrowsingContext); + if (mozilla::SessionHistoryInParent() && mLoadingEntry) { + // We keep the loading entry for the load that failed here. If the user + // reloads we want to try to reload the original load, not the error page. + loadState->SetLoadingSessionHistoryInfo( + MakeUnique(*mLoadingEntry)); + } + return InternalLoad(loadState); +} + +NS_IMETHODIMP +nsDocShell::Reload(uint32_t aReloadFlags) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + NS_ASSERTION(((aReloadFlags & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0), + "Reload command not updated to use load flags!"); + NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0, + "Don't pass these flags to Reload"); + + uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags); + NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG); + + // Send notifications to the HistoryListener if any, about the impending + // reload + RefPtr rootSH = GetRootSessionHistory(); + if (mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this)); + bool forceReload = IsForceReloadType(loadType); + if (!XRE_IsParentProcess()) { + ++mPendingReloadCount; + RefPtr docShell(this); + nsCOMPtr cv(mContentViewer); + NS_ENSURE_STATE(cv); + + bool okToUnload = true; + MOZ_TRY(cv->PermitUnload(&okToUnload)); + if (!okToUnload) { + return NS_OK; + } + + RefPtr doc(GetDocument()); + RefPtr browsingContext(mBrowsingContext); + nsCOMPtr currentURI(mCurrentURI); + nsCOMPtr referrerInfo(mReferrerInfo); + RefPtr stopDetector = new StopDetector(); + nsCOMPtr loadGroup; + GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + // loadGroup may be null in theory. In that case stopDetector just + // doesn't do anything. + loadGroup->AddRequest(stopDetector, nullptr); + } + + ContentChild::GetSingleton()->SendNotifyOnHistoryReload( + mBrowsingContext, forceReload, + [docShell, doc, loadType, browsingContext, currentURI, referrerInfo, + loadGroup, stopDetector]( + std::tuple>>, + Maybe>&& aResult) { + auto scopeExit = MakeScopeExit([loadGroup, stopDetector]() { + if (loadGroup) { + loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); + } + }); + + // Decrease mPendingReloadCount before any other early returns! + if (--(docShell->mPendingReloadCount) > 0) { + return; + } + + if (stopDetector->Canceled()) { + return; + } + bool canReload; + Maybe>> loadState; + Maybe reloadingActiveEntry; + + std::tie(canReload, loadState, reloadingActiveEntry) = aResult; + + if (!canReload) { + return; + } + + if (loadState.isSome()) { + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("nsDocShell %p Reload - LoadHistoryEntry", docShell.get())); + loadState.ref()->SetNotifiedBeforeUnloadListeners(true); + docShell->LoadHistoryEntry(loadState.ref(), loadType, + reloadingActiveEntry.ref()); + } else { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p ReloadDocument", docShell.get())); + ReloadDocument(docShell, doc, loadType, browsingContext, + currentURI, referrerInfo, + /* aNotifiedBeforeUnloadListeners */ true); + } + }, + [](mozilla::ipc::ResponseRejectReason) {}); + } else { + // Parent process + bool canReload = false; + Maybe>> loadState; + Maybe reloadingActiveEntry; + if (!mBrowsingContext->IsDiscarded()) { + mBrowsingContext->Canonical()->NotifyOnHistoryReload( + forceReload, canReload, loadState, reloadingActiveEntry); + } + if (canReload) { + if (loadState.isSome()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p Reload - LoadHistoryEntry", this)); + LoadHistoryEntry(loadState.ref(), loadType, + reloadingActiveEntry.ref()); + } else { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p ReloadDocument", this)); + RefPtr doc = GetDocument(); + RefPtr bc = mBrowsingContext; + nsCOMPtr currentURI = mCurrentURI; + nsCOMPtr referrerInfo = mReferrerInfo; + ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo); + } + } + } + return NS_OK; + } + + bool canReload = true; + if (rootSH) { + rootSH->LegacySHistory()->NotifyOnHistoryReload(&canReload); + } + + if (!canReload) { + return NS_OK; + } + + /* If you change this part of code, make sure bug 45297 does not re-occur */ + if (mOSHE) { + nsCOMPtr oshe = mOSHE; + return LoadHistoryEntry( + oshe, loadType, + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); + } + + if (mLSHE) { // In case a reload happened before the current load is done + nsCOMPtr lshe = mLSHE; + return LoadHistoryEntry( + lshe, loadType, + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); + } + + RefPtr doc = GetDocument(); + RefPtr bc = mBrowsingContext; + nsCOMPtr currentURI = mCurrentURI; + nsCOMPtr referrerInfo = mReferrerInfo; + return ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo); +} + +/* static */ +nsresult nsDocShell::ReloadDocument(nsDocShell* aDocShell, Document* aDocument, + uint32_t aLoadType, + BrowsingContext* aBrowsingContext, + nsIURI* aCurrentURI, + nsIReferrerInfo* aReferrerInfo, + bool aNotifiedBeforeUnloadListeners) { + if (!aDocument) { + return NS_OK; + } + + // Do not inherit owner from document + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + nsAutoString srcdoc; + nsIURI* baseURI = nullptr; + nsCOMPtr originalURI; + nsCOMPtr resultPrincipalURI; + bool loadReplace = false; + + nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal(); + nsCOMPtr csp = aDocument->GetCsp(); + uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags(); + + nsAutoString contentTypeHint; + aDocument->GetContentType(contentTypeHint); + + if (aDocument->IsSrcdocDocument()) { + aDocument->GetSrcdocData(srcdoc); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + baseURI = aDocument->GetBaseURI(); + } else { + srcdoc = VoidString(); + } + nsCOMPtr chan = aDocument->GetChannel(); + if (chan) { + uint32_t loadFlags; + chan->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + nsCOMPtr httpChan(do_QueryInterface(chan)); + if (httpChan) { + httpChan->GetOriginalURI(getter_AddRefs(originalURI)); + } + + nsCOMPtr loadInfo = chan->LoadInfo(); + loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); + } + + if (!triggeringPrincipal) { + MOZ_ASSERT(false, "Reload needs a valid triggeringPrincipal"); + return NS_ERROR_FAILURE; + } + + // Stack variables to ensure changes to the member variables don't affect to + // the call. + nsCOMPtr currentURI = aCurrentURI; + + // Reload always rewrites result principal URI. + Maybe> emplacedResultPrincipalURI; + emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI)); + + RefPtr context = aBrowsingContext->GetCurrentWindowContext(); + RefPtr loadState = new nsDocShellLoadState(currentURI); + loadState->SetReferrerInfo(aReferrerInfo); + loadState->SetOriginalURI(originalURI); + loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI); + loadState->SetLoadReplace(loadReplace); + loadState->SetTriggeringPrincipal(triggeringPrincipal); + loadState->SetTriggeringSandboxFlags(triggeringSandboxFlags); + loadState->SetPrincipalToInherit(triggeringPrincipal); + loadState->SetCsp(csp); + loadState->SetInternalLoadFlags(flags); + loadState->SetTypeHint(NS_ConvertUTF16toUTF8(contentTypeHint)); + loadState->SetLoadType(aLoadType); + loadState->SetFirstParty(true); + loadState->SetSrcdocData(srcdoc); + loadState->SetSourceBrowsingContext(aBrowsingContext); + loadState->SetBaseURI(baseURI); + loadState->SetHasValidUserGestureActivation( + context && context->HasValidTransientUserGestureActivation()); + loadState->SetNotifiedBeforeUnloadListeners(aNotifiedBeforeUnloadListeners); + return aDocShell->InternalLoad(loadState); +} + +NS_IMETHODIMP +nsDocShell::Stop(uint32_t aStopFlags) { + // Revoke any pending event related to content viewer restoration + mRestorePresentationEvent.Revoke(); + + if (mLoadType == LOAD_ERROR_PAGE) { + if (mLSHE) { + // Since error page loads never unset mLSHE, do so now + SetHistoryEntryAndUpdateBC(Some(nullptr), Some(mLSHE)); + } + mActiveEntryIsLoadingFromSessionHistory = false; + + mFailedChannel = nullptr; + mFailedURI = nullptr; + } + + if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { + // Stop the document loading and animations + if (mContentViewer) { + nsCOMPtr cv = mContentViewer; + cv->Stop(); + } + } else if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { + // Stop the document loading only + if (mContentViewer) { + RefPtr doc = mContentViewer->GetDocument(); + if (doc) { + doc->StopDocumentLoad(); + } + } + } + + if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { + // Suspend any timers that were set for this loader. We'll clear + // them out for good in CreateContentViewer. + if (mRefreshURIList) { + SuspendRefreshURIs(); + mSavedRefreshURIList.swap(mRefreshURIList); + mRefreshURIList = nullptr; + } + + // XXXbz We could also pass |this| to nsIURILoader::Stop. That will + // just call Stop() on us as an nsIDocumentLoader... We need fewer + // redundant apis! + Stop(); + + // Clear out mChannelToDisconnectOnPageHide. This page won't go in the + // BFCache now, and the Stop above will have removed the DocumentChannel + // from the loadgroup. + mChannelToDisconnectOnPageHide = 0; + } + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shellAsNav(do_QueryObject(child)); + if (shellAsNav) { + shellAsNav->Stop(aStopFlags); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDocument(Document** aDocument) { + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE); + + RefPtr doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + + doc.forget(aDocument); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentURI(nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr uri = mCurrentURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) { + NS_ENSURE_ARG_POINTER(aSessionHistory); + RefPtr shistory = GetSessionHistory(); + shistory.forget(aSessionHistory); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebPageDescriptor +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::LoadPageAsViewSource(nsIDocShell* aOtherDocShell, + const nsAString& aURI) { + if (!aOtherDocShell) { + return NS_ERROR_INVALID_POINTER; + } + nsCOMPtr newURI; + nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr loadState; + uint32_t cacheKey; + auto* otherDocShell = nsDocShell::Cast(aOtherDocShell); + if (mozilla::SessionHistoryInParent()) { + loadState = new nsDocShellLoadState(newURI); + if (!otherDocShell->FillLoadStateFromCurrentEntry(*loadState)) { + return NS_ERROR_INVALID_POINTER; + } + cacheKey = otherDocShell->GetCacheKeyFromCurrentEntry().valueOr(0); + } else { + nsCOMPtr entry; + bool isOriginalSHE; + otherDocShell->GetCurrentSHEntry(getter_AddRefs(entry), &isOriginalSHE); + if (!entry) { + return NS_ERROR_INVALID_POINTER; + } + rv = entry->CreateLoadInfo(getter_AddRefs(loadState)); + NS_ENSURE_SUCCESS(rv, rv); + entry->GetCacheKey(&cacheKey); + loadState->SetURI(newURI); + loadState->SetSHEntry(nullptr); + } + + // We're doing a load of the page, via an API that + // is only exposed to system code. The triggering principal for this load + // should be the system principal. + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + loadState->SetOriginalURI(nullptr); + loadState->SetResultPrincipalURI(nullptr); + + return InternalLoad(loadState, Some(cacheKey)); +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) { + MOZ_ASSERT(aPageDescriptor, "Null out param?"); + + *aPageDescriptor = nullptr; + + nsISHEntry* src = mOSHE ? mOSHE : mLSHE; + if (src) { + nsCOMPtr dest; + + nsresult rv = src->Clone(getter_AddRefs(dest)); + if (NS_FAILED(rv)) { + return rv; + } + + // null out inappropriate cloned attributes... + dest->SetParent(nullptr); + dest->SetIsSubFrame(false); + + return CallQueryInterface(dest, aPageDescriptor); + } + + return NS_ERROR_NOT_AVAILABLE; +} + +already_AddRefed nsDocShell::GetPostDataFromCurrentEntry() + const { + nsCOMPtr postData; + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + postData = mActiveEntry->GetPostData(); + } else if (mLoadingEntry) { + postData = mLoadingEntry->mInfo.GetPostData(); + } + } else { + if (mOSHE) { + postData = mOSHE->GetPostData(); + } else if (mLSHE) { + postData = mLSHE->GetPostData(); + } + } + + return postData.forget(); +} + +Maybe nsDocShell::GetCacheKeyFromCurrentEntry() const { + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + return Some(mActiveEntry->GetCacheKey()); + } + + if (mLoadingEntry) { + return Some(mLoadingEntry->mInfo.GetCacheKey()); + } + } else { + if (mOSHE) { + return Some(mOSHE->GetCacheKey()); + } + + if (mLSHE) { + return Some(mLSHE->GetCacheKey()); + } + } + + return Nothing(); +} + +bool nsDocShell::FillLoadStateFromCurrentEntry( + nsDocShellLoadState& aLoadState) { + if (mLoadingEntry) { + mLoadingEntry->mInfo.FillLoadInfo(aLoadState); + return true; + } + if (mActiveEntry) { + mActiveEntry->FillLoadInfo(aLoadState); + return true; + } + return false; +} + +//***************************************************************************** +// nsDocShell::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* aParentWidget, int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight) { + SetParentWidget(aParentWidget); + SetPositionAndSize(aX, aY, aWidth, aHeight, 0); + NS_ENSURE_TRUE(Initialize(), NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::Destroy() { + // XXX: We allow this function to be called just once. If you are going to + // reset new variables in this function, please make sure the variables will + // never be re-initialized. Adding assertions to check |mIsBeingDestroyed| + // in the setter functions for the variables would be enough. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + nsCOMPtr serv = services::GetObserverService(); + if (serv) { + const char* msg = mItemType == typeContent + ? NS_WEBNAVIGATION_DESTROY + : NS_CHROME_WEBNAVIGATION_DESTROY; + serv->NotifyObservers(GetAsSupports(this), msg, nullptr); + } + + mIsBeingDestroyed = true; + + // Brak the cycle with the initial client, if present. + mInitialClientSource.reset(); + + // Make sure we don't record profile timeline markers anymore + SetRecordProfileTimelineMarkers(false); + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Fire unload event before we blow anything away. + (void)FirePageHideNotification(true); + + // Clear pointers to any detached nsEditorData that's lying + // around in shistory entries. Breaks cycle. See bug 430921. + if (mOSHE) { + mOSHE->SetEditorData(nullptr); + } + if (mLSHE) { + mLSHE->SetEditorData(nullptr); + } + + // Note: mContentListener can be null if Init() failed and we're being + // called from the destructor. + if (mContentListener) { + mContentListener->DropDocShellReference(); + mContentListener->SetParentContentListener(nullptr); + // Note that we do NOT set mContentListener to null here; that + // way if someone tries to do a load in us after this point + // the nsDSURIContentListener will block it. All of which + // means that we should do this before calling Stop(), of + // course. + } + + // Stop any URLs that are currently being loaded... + Stop(nsIWebNavigation::STOP_ALL); + + mEditorData = nullptr; + + // Save the state of the current document, before destroying the window. + // This is needed to capture the state of a frameset when the new document + // causes the frameset to be destroyed... + PersistLayoutHistoryState(); + + // Remove this docshell from its parent's child list + nsCOMPtr docShellParentAsItem = + do_QueryInterface(GetAsSupports(mParent)); + if (docShellParentAsItem) { + docShellParentAsItem->RemoveChild(this); + } + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + nsDocLoader::Destroy(); + + mParentWidget = nullptr; + mCurrentURI = nullptr; + + if (mScriptGlobal) { + mScriptGlobal->DetachFromDocShell(!mWillChangeProcess); + mScriptGlobal = nullptr; + } + + if (GetSessionHistory()) { + // We want to destroy these content viewers now rather than + // letting their destruction wait for the session history + // entries to get garbage collected. (Bug 488394) + GetSessionHistory()->EvictLocalContentViewers(); + } + + if (mWillChangeProcess && !mBrowsingContext->IsDiscarded()) { + mBrowsingContext->PrepareForProcessChange(); + } + + SetTreeOwner(nullptr); + + mBrowserChild = nullptr; + + mChromeEventHandler = nullptr; + + // Cancel any timers that were set for this docshell; this is needed + // to break the cycle between us and the timers. + CancelRefreshURITimers(); + + return NS_OK; +} + +double nsDocShell::GetWidgetCSSToDeviceScale() { + if (mParentWidget) { + return mParentWidget->GetDefaultScale().scale; + } + if (nsCOMPtr ownerWindow = do_QueryInterface(mTreeOwner)) { + return ownerWindow->GetWidgetCSSToDeviceScale(); + } + return 1.0; +} + +NS_IMETHODIMP +nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) { + if (mParentWidget) { + *aScale = mParentWidget->GetDesktopToDeviceScale().scale; + return NS_OK; + } + + nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->GetDevicePixelsPerDesktopPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPosition(int32_t aX, int32_t aY) { + mBounds.MoveTo(aX, aY); + + if (mContentViewer) { + NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) { + nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->SetPositionDesktopPix(aX, aY); + } + + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); +} + +NS_IMETHODIMP +nsDocShell::GetPosition(int32_t* aX, int32_t* aY) { + return GetPositionAndSize(aX, aY, nullptr, nullptr); +} + +NS_IMETHODIMP +nsDocShell::SetSize(int32_t aWidth, int32_t aHeight, bool aRepaint) { + int32_t x = 0, y = 0; + GetPosition(&x, &y); + return SetPositionAndSize(x, y, aWidth, aHeight, + aRepaint ? nsIBaseWindow::eRepaint : 0); +} + +NS_IMETHODIMP +nsDocShell::GetSize(int32_t* aWidth, int32_t* aHeight) { + return GetPositionAndSize(nullptr, nullptr, aWidth, aHeight); +} + +NS_IMETHODIMP +nsDocShell::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aWidth, + int32_t aHeight, uint32_t aFlags) { + mBounds.SetRect(aX, aY, aWidth, aHeight); + + // Hold strong ref, since SetBounds can make us null out mContentViewer + nsCOMPtr viewer = mContentViewer; + if (viewer) { + uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize) + ? nsIContentViewer::eDelayResize + : 0; + // XXX Border figured in here or is that handled elsewhere? + nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + if (mParentWidget) { + // ensure size is up-to-date if window has changed resolution + LayoutDeviceIntRect r = mParentWidget->GetClientBounds(); + SetPositionAndSize(mBounds.X(), mBounds.Y(), r.Width(), r.Height(), 0); + } + + // We should really consider just getting this information from + // our window instead of duplicating the storage and code... + if (aWidth || aHeight) { + // Caller wants to know our size; make sure to give them up to + // date information. + RefPtr doc(do_GetInterface(GetAsSupports(mParent))); + if (doc) { + doc->FlushPendingNotifications(FlushType::Layout); + } + } + + DoGetPositionAndSize(aX, aY, aWidth, aHeight); + return NS_OK; +} + +void nsDocShell::DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + if (aX) { + *aX = mBounds.X(); + } + if (aY) { + *aY = mBounds.Y(); + } + if (aWidth) { + *aWidth = mBounds.Width(); + } + if (aHeight) { + *aHeight = mBounds.Height(); + } +} + +NS_IMETHODIMP +nsDocShell::SetDimensions(DimensionRequest&& aRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, + int32_t* aY, int32_t* aCX, int32_t* aCY) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::Repaint(bool aForce) { + PresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + RefPtr viewManager = presShell->GetViewManager(); + NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE); + + viewManager->InvalidateAllViews(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParentWidget(nsIWidget** aParentWidget) { + NS_ENSURE_ARG_POINTER(aParentWidget); + + *aParentWidget = mParentWidget; + NS_IF_ADDREF(*aParentWidget); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetParentWidget(nsIWidget* aParentWidget) { + MOZ_ASSERT(!mIsBeingDestroyed); + mParentWidget = aParentWidget; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParentNativeWindow(nativeWindow* aParentNativeWindow) { + NS_ENSURE_ARG_POINTER(aParentNativeWindow); + + if (mParentWidget) { + *aParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET); + } else { + *aParentNativeWindow = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetParentNativeWindow(nativeWindow aParentNativeWindow) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetNativeHandle(nsAString& aNativeHandle) { + // the nativeHandle should be accessed from nsIAppWindow + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetVisibility(bool* aVisibility) { + NS_ENSURE_ARG_POINTER(aVisibility); + + *aVisibility = false; + + if (!mContentViewer) { + return NS_OK; + } + + PresShell* presShell = GetPresShell(); + if (!presShell) { + return NS_OK; + } + + // get the view manager + nsViewManager* vm = presShell->GetViewManager(); + NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); + + // get the root view + nsView* view = vm->GetRootView(); // views are not ref counted + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + + // if our root view is hidden, we are not visible + if (view->GetVisibility() == ViewVisibility::Hide) { + return NS_OK; + } + + // otherwise, we must walk up the document and view trees checking + // for a hidden view, unless we're an off screen browser, which + // would make this test meaningless. + + RefPtr docShell = this; + RefPtr parentItem = docShell->GetInProcessParentDocshell(); + while (parentItem) { + // Null-check for crash in bug 267804 + if (!parentItem->GetPresShell()) { + MOZ_ASSERT_UNREACHABLE("parent docshell has null pres shell"); + return NS_OK; + } + + vm = docShell->GetPresShell()->GetViewManager(); + if (vm) { + view = vm->GetRootView(); + } + + if (view) { + view = view->GetParent(); // anonymous inner view + if (view) { + view = view->GetParent(); // subdocumentframe's view + } + } + + nsIFrame* frame = view ? view->GetFrame() : nullptr; + if (frame && !frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { + return NS_OK; + } + + docShell = parentItem; + parentItem = docShell->GetInProcessParentDocshell(); + } + + nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (!treeOwnerAsWin) { + *aVisibility = true; + return NS_OK; + } + + // Check with the tree owner as well to give embedders a chance to + // expose visibility as well. + nsresult rv = treeOwnerAsWin->GetVisibility(aVisibility); + if (rv == NS_ERROR_NOT_IMPLEMENTED) { + // The tree owner had no opinion on our visibility. + *aVisibility = true; + return NS_OK; + } + return rv; +} + +void nsDocShell::ActivenessMaybeChanged() { + const bool isActive = mBrowsingContext->IsActive(); + if (RefPtr presShell = GetPresShell()) { + presShell->ActivenessMaybeChanged(); + } + + // Tell the window about it + if (mScriptGlobal) { + mScriptGlobal->SetIsBackground(!isActive); + if (RefPtr doc = mScriptGlobal->GetExtantDoc()) { + // Update orientation when the top-level browsing context becomes active. + if (isActive && mBrowsingContext->IsTop()) { + // We only care about the top-level browsing context. + auto orientation = mBrowsingContext->GetOrientationLock(); + ScreenOrientation::UpdateActiveOrientationLock(orientation); + } + + doc->PostVisibilityUpdateEvent(); + } + } + + // Tell the nsDOMNavigationTiming about it + RefPtr timing = mTiming; + if (!timing && mContentViewer) { + if (Document* doc = mContentViewer->GetDocument()) { + timing = doc->GetNavigationTiming(); + } + } + if (timing) { + timing->NotifyDocShellStateChanged( + isActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + } + + // Restart or stop meta refresh timers if necessary + if (mDisableMetaRefreshWhenInactive) { + if (isActive) { + ResumeRefreshURIs(); + } else { + SuspendRefreshURIs(); + } + } + + if (InputTaskManager::CanSuspendInputEvent()) { + mBrowsingContext->Group()->UpdateInputTaskManagerIfNeeded(isActive); + } +} + +NS_IMETHODIMP +nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) { + if (!mWillChangeProcess) { + // Intentionally ignoring handling discarded browsing contexts. + Unused << mBrowsingContext->SetDefaultLoadFlags(aDefaultLoadFlags); + } else { + // Bug 1623565: DevTools tries to clean up defaultLoadFlags on + // shutdown. Sorry DevTools, your DocShell is in another process. + NS_WARNING("nsDocShell::SetDefaultLoadFlags called on Zombie DocShell"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) { + *aDefaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) { + NS_ENSURE_ARG_POINTER(aFailedChannel); + Document* doc = GetDocument(); + if (!doc) { + *aFailedChannel = nullptr; + return NS_OK; + } + NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel()); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetVisibility(bool aVisibility) { + // Show()/Hide() may change mContentViewer. + nsCOMPtr cv = mContentViewer; + if (!cv) { + return NS_OK; + } + if (aVisibility) { + cv->Show(); + } else { + cv->Hide(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetEnabled(bool* aEnabled) { + NS_ENSURE_ARG_POINTER(aEnabled); + *aEnabled = true; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::SetEnabled(bool aEnabled) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsDocShell::GetMainWidget(nsIWidget** aMainWidget) { + // We don't create our own widget, so simply return the parent one. + return GetParentWidget(aMainWidget); +} + +NS_IMETHODIMP +nsDocShell::GetTitle(nsAString& aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTitle(const nsAString& aTitle) { + // Avoid unnecessary updates of the title if the URI and the title haven't + // changed. + if (mTitleValidForCurrentURI && mTitle == aTitle) { + return NS_OK; + } + + // Store local title + mTitle = aTitle; + mTitleValidForCurrentURI = true; + + // When title is set on the top object it should then be passed to the + // tree owner. + if (mBrowsingContext->IsTop()) { + nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (treeOwnerAsWin) { + treeOwnerAsWin->SetTitle(aTitle); + } + } + + if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) { + UpdateGlobalHistoryTitle(mCurrentURI); + } + + // Update SessionHistory with the document's title. + if (mLoadType != LOAD_BYPASS_HISTORY && mLoadType != LOAD_ERROR_PAGE) { + SetTitleOnHistoryEntry(true); + } + + return NS_OK; +} + +void nsDocShell::SetTitleOnHistoryEntry(bool aUpdateEntryInSessionHistory) { + if (mOSHE) { + mOSHE->SetTitle(mTitle); + } + + if (mActiveEntry && mBrowsingContext) { + mActiveEntry->SetTitle(mTitle); + if (aUpdateEntryInSessionHistory) { + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetTitle(mTitle); + } + } else { + mozilla::Unused + << ContentChild::GetSingleton()->SendSessionHistoryEntryTitle( + mBrowsingContext, mTitle); + } + } + } +} + +nsPoint nsDocShell::GetCurScrollPos() { + nsPoint scrollPos; + if (nsIScrollableFrame* sf = GetRootScrollFrame()) { + scrollPos = sf->GetVisualViewportOffset(); + } + return scrollPos; +} + +nsresult nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos, + int32_t aCurVerticalPos) { + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + ScrollMode scrollMode = + sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant; + + nsPoint targetPos(aCurHorizontalPos, aCurVerticalPos); + sf->ScrollTo(targetPos, scrollMode); + + // Set the visual viewport offset as well. + + RefPtr presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + // Only the root content document can have a distinct visual viewport offset. + if (!presContext->IsRootContentDocumentCrossProcess()) { + return NS_OK; + } + + // Not on a platform with a distinct visual viewport - don't bother setting + // the visual viewport offset. + if (!presShell->IsVisualViewportSizeSet()) { + return NS_OK; + } + + presShell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread, + scrollMode); + + return NS_OK; +} + +void nsDocShell::SetScrollbarPreference(mozilla::ScrollbarPreference aPref) { + if (mScrollbarPref == aPref) { + return; + } + mScrollbarPref = aPref; + auto* ps = GetPresShell(); + if (!ps) { + return; + } + nsIFrame* scrollFrame = ps->GetRootScrollFrame(); + if (!scrollFrame) { + return; + } + ps->FrameNeedsReflow(scrollFrame, + IntrinsicDirty::FrameAncestorsAndDescendants, + NS_FRAME_IS_DIRTY); +} + +//***************************************************************************** +// nsDocShell::nsIRefreshURI +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, + uint32_t aDelay) { + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ENSURE_ARG(aURI); + + /* Check if Meta refresh/redirects are permitted. Some + * embedded applications may not want to do this. + * Must do this before sending out NOTIFY_REFRESH events + * because listeners may have side effects (e.g. displaying a + * button to manually trigger the refresh later). + */ + bool allowRedirects = true; + GetAllowMetaRedirects(&allowRedirects); + if (!allowRedirects) { + return NS_OK; + } + + // If any web progress listeners are listening for NOTIFY_REFRESH events, + // give them a chance to block this refresh. + bool sameURI; + nsresult rv = aURI->Equals(mCurrentURI, &sameURI); + if (NS_FAILED(rv)) { + sameURI = false; + } + if (!RefreshAttempted(this, aURI, aDelay, sameURI)) { + return NS_OK; + } + + nsCOMPtr refreshTimer = + new nsRefreshTimer(this, aURI, aPrincipal, aDelay); + + BusyFlags busyFlags = GetBusyFlags(); + + if (!mRefreshURIList) { + mRefreshURIList = nsArray::Create(); + } + + if (busyFlags & BUSY_FLAGS_BUSY || + (!mBrowsingContext->IsActive() && mDisableMetaRefreshWhenInactive)) { + // We don't want to create the timer right now. Instead queue up the + // request and trigger the timer in EndPageLoad() or whenever we become + // active. + mRefreshURIList->AppendElement(refreshTimer); + } else { + // There is no page loading going on right now. Create the + // timer and fire it right away. + nsCOMPtr win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + nsCOMPtr timer; + MOZ_TRY_VAR(timer, NS_NewTimerWithCallback(refreshTimer, aDelay, + nsITimer::TYPE_ONE_SHOT)); + + mRefreshURIList->AppendElement(timer); // owning timer ref + } + return NS_OK; +} + +nsresult nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI, + nsIPrincipal* aPrincipal, + uint32_t aDelay, + nsITimer* aTimer) { + MOZ_ASSERT(aTimer, "Must have a timer here"); + + // Remove aTimer from mRefreshURIList if needed + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); + if (timer == aTimer) { + mRefreshURIList->RemoveElementAt(i); + break; + } + } + } + + return ForceRefreshURI(aURI, aPrincipal, aDelay); +} + +NS_IMETHODIMP +nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, + uint32_t aDelay) { + NS_ENSURE_ARG(aURI); + + RefPtr loadState = new nsDocShellLoadState(aURI); + loadState->SetOriginalURI(mCurrentURI); + loadState->SetResultPrincipalURI(aURI); + loadState->SetResultPrincipalURIIsSome(true); + loadState->SetKeepResultPrincipalURIIfSet(true); + loadState->SetIsMetaRefresh(true); + + // Set the triggering pricipal to aPrincipal if available, or current + // document's principal otherwise. + nsCOMPtr principal = aPrincipal; + RefPtr doc = GetDocument(); + if (!principal) { + if (!doc) { + return NS_ERROR_FAILURE; + } + principal = doc->NodePrincipal(); + } + loadState->SetTriggeringPrincipal(principal); + if (doc) { + loadState->SetCsp(doc->GetCsp()); + loadState->SetHasValidUserGestureActivation( + doc->HasValidTransientUserGestureActivation()); + loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags()); + } + + loadState->SetPrincipalIsExplicit(true); + + /* Check if this META refresh causes a redirection + * to another site. + */ + bool equalUri = false; + nsresult rv = aURI->Equals(mCurrentURI, &equalUri); + + nsCOMPtr referrerInfo; + if (NS_SUCCEEDED(rv) && !equalUri && aDelay <= REFRESH_REDIRECT_TIMER) { + /* It is a META refresh based redirection within the threshold time + * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER). + * Pass a REPLACE flag to LoadURI(). + */ + loadState->SetLoadType(LOAD_REFRESH_REPLACE); + + /* For redirects we mimic HTTP, which passes the + * original referrer. + * We will pass in referrer but will not send to server + */ + if (mReferrerInfo) { + referrerInfo = static_cast(mReferrerInfo.get()) + ->CloneWithNewSendReferrer(false); + } + } else { + loadState->SetLoadType(LOAD_REFRESH); + /* We do need to pass in a referrer, but we don't want it to + * be sent to the server. + * For most refreshes the current URI is an appropriate + * internal referrer. + */ + referrerInfo = new ReferrerInfo(mCurrentURI, ReferrerPolicy::_empty, false); + } + + loadState->SetReferrerInfo(referrerInfo); + loadState->SetLoadFlags( + nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL); + loadState->SetFirstParty(true); + + /* + * LoadURI(...) will cancel all refresh timers... This causes the + * Timer and its refreshData instance to be released... + */ + LoadURI(loadState, false); + + return NS_OK; +} + +static const char16_t* SkipASCIIWhitespace(const char16_t* aStart, + const char16_t* aEnd) { + const char16_t* iter = aStart; + while (iter != aEnd && mozilla::IsAsciiWhitespace(*iter)) { + ++iter; + } + return iter; +} + +static std::tuple ExtractURLString( + const char16_t* aPosition, const char16_t* aEnd) { + MOZ_ASSERT(aPosition != aEnd); + + // 1. Let urlString be the substring of input from the code point at + // position to the end of the string. + const char16_t* urlStart = aPosition; + const char16_t* urlEnd = aEnd; + + // 2. If the code point in input pointed to by position is U+0055 (U) or + // U+0075 (u), then advance position to the next code point. + // Otherwise, jump to the step labeled skip quotes. + if (*aPosition == 'U' || *aPosition == 'u') { + ++aPosition; + + // 3. If the code point in input pointed to by position is U+0052 (R) or + // U+0072 (r), then advance position to the next code point. + // Otherwise, jump to the step labeled parse. + if (aPosition == aEnd || (*aPosition != 'R' && *aPosition != 'r')) { + return std::make_tuple(urlStart, urlEnd); + } + + ++aPosition; + + // 4. If the code point in input pointed to by position is U+004C (L) or + // U+006C (l), then advance position to the next code point. + // Otherwise, jump to the step labeled parse. + if (aPosition == aEnd || (*aPosition != 'L' && *aPosition != 'l')) { + return std::make_tuple(urlStart, urlEnd); + } + + ++aPosition; + + // 5. Skip ASCII whitespace within input given position. + aPosition = SkipASCIIWhitespace(aPosition, aEnd); + + // 6. If the code point in input pointed to by position is U+003D (=), + // then advance position to the next code point. Otherwise, jump to + // the step labeled parse. + if (aPosition == aEnd || *aPosition != '=') { + return std::make_tuple(urlStart, urlEnd); + } + + ++aPosition; + + // 7. Skip ASCII whitespace within input given position. + aPosition = SkipASCIIWhitespace(aPosition, aEnd); + } + + // 8. Skip quotes: If the code point in input pointed to by position is + // U+0027 (') or U+0022 ("), then let quote be that code point, and + // advance position to the next code point. Otherwise, let quote be + // the empty string. + Maybe quote; + if (aPosition != aEnd && (*aPosition == '\'' || *aPosition == '"')) { + quote.emplace(*aPosition); + ++aPosition; + } + + // 9. Set urlString to the substring of input from the code point at + // position to the end of the string. + urlStart = aPosition; + urlEnd = aEnd; + + // 10. If quote is not the empty string, and there is a code point in + // urlString equal to quote, then truncate urlString at that code + // point, so that it and all subsequent code points are removed. + const char16_t* quotePos; + if (quote.isSome() && + (quotePos = nsCharTraits::find( + urlStart, std::distance(urlStart, aEnd), quote.value()))) { + urlEnd = quotePos; + } + + return std::make_tuple(urlStart, urlEnd); +} + +void nsDocShell::SetupRefreshURIFromHeader(Document* aDocument, + const nsAString& aHeader) { + if (mIsBeingDestroyed) { + return; + } + + const char16_t* position = aHeader.BeginReading(); + const char16_t* end = aHeader.EndReading(); + + // See + // https://html.spec.whatwg.org/#pragma-directives:shared-declarative-refresh-steps. + + // 3. Skip ASCII whitespace + position = SkipASCIIWhitespace(position, end); + + // 4. Let time be 0. + CheckedInt milliSeconds; + + // 5. Collect a sequence of code points that are ASCII digits + const char16_t* digitsStart = position; + while (position != end && mozilla::IsAsciiDigit(*position)) { + ++position; + } + + if (position == digitsStart) { + // 6. If timeString is the empty string, then: + // 1. If the code point in input pointed to by position is not U+002E + // (.), then return. + if (position == end || *position != '.') { + return; + } + } else { + // 7. Otherwise, set time to the result of parsing timeString using the + // rules for parsing non-negative integers. + nsContentUtils::ParseHTMLIntegerResultFlags result; + uint32_t seconds = + nsContentUtils::ParseHTMLInteger(digitsStart, position, &result); + MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_Negative)); + if (result & nsContentUtils::eParseHTMLInteger_Error) { + // The spec assumes no errors here (since we only pass ASCII digits in), + // but we can still overflow, so this block should deal with that (and + // only that). + MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_ErrorOverflow)); + return; + } + MOZ_ASSERT( + !(result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)); + + milliSeconds = seconds; + milliSeconds *= 1000; + if (!milliSeconds.isValid()) { + return; + } + } + + // 8. Collect a sequence of code points that are ASCII digits and U+002E FULL + // STOP characters (.) from input given position. Ignore any collected + // characters. + while (position != end && + (mozilla::IsAsciiDigit(*position) || *position == '.')) { + ++position; + } + + // 9. Let urlRecord be document's URL. + nsCOMPtr urlRecord(aDocument->GetDocumentURI()); + + // 10. If position is not past the end of input + if (position != end) { + // 1. If the code point in input pointed to by position is not U+003B (;), + // U+002C (,), or ASCII whitespace, then return. + if (*position != ';' && *position != ',' && + !mozilla::IsAsciiWhitespace(*position)) { + return; + } + + // 2. Skip ASCII whitespace within input given position. + position = SkipASCIIWhitespace(position, end); + + // 3. If the code point in input pointed to by position is U+003B (;) or + // U+002C (,), then advance position to the next code point. + if (position != end && (*position == ';' || *position == ',')) { + ++position; + + // 4. Skip ASCII whitespace within input given position. + position = SkipASCIIWhitespace(position, end); + } + + // 11. If position is not past the end of input, then: + if (position != end) { + const char16_t* urlStart; + const char16_t* urlEnd; + + // 1-10. See ExtractURLString. + std::tie(urlStart, urlEnd) = ExtractURLString(position, end); + + // 11. Parse: Parse urlString relative to document. If that fails, return. + // Otherwise, set urlRecord to the resulting URL record. + nsresult rv = + NS_NewURI(getter_AddRefs(urlRecord), + Substring(urlStart, std::distance(urlStart, urlEnd)), + /* charset = */ nullptr, aDocument->GetDocBaseURI()); + NS_ENSURE_SUCCESS_VOID(rv); + } + } + + nsIPrincipal* principal = aDocument->NodePrincipal(); + nsCOMPtr securityManager = + nsContentUtils::GetSecurityManager(); + nsresult rv = securityManager->CheckLoadURIWithPrincipal( + principal, urlRecord, + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT, + aDocument->InnerWindowID()); + NS_ENSURE_SUCCESS_VOID(rv); + + bool isjs = true; + rv = NS_URIChainHasFlags( + urlRecord, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); + NS_ENSURE_SUCCESS_VOID(rv); + + if (isjs) { + return; + } + + RefreshURI(urlRecord, principal, milliSeconds.value()); +} + +static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) { + if (!aTimerList) { + return; + } + + uint32_t n = 0; + aTimerList->GetLength(&n); + + while (n) { + nsCOMPtr timer(do_QueryElementAt(aTimerList, --n)); + + aTimerList->RemoveElementAt(n); // bye bye owning timer ref + + if (timer) { + timer->Cancel(); + } + } +} + +NS_IMETHODIMP +nsDocShell::CancelRefreshURITimers() { + DoCancelRefreshURITimers(mRefreshURIList); + DoCancelRefreshURITimers(mSavedRefreshURIList); + DoCancelRefreshURITimers(mBFCachedRefreshURIList); + mRefreshURIList = nullptr; + mSavedRefreshURIList = nullptr; + mBFCachedRefreshURIList = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRefreshPending(bool* aResult) { + if (!mRefreshURIList) { + *aResult = false; + return NS_OK; + } + + uint32_t count; + nsresult rv = mRefreshURIList->GetLength(&count); + if (NS_SUCCEEDED(rv)) { + *aResult = (count != 0); + } + return rv; +} + +void nsDocShell::RefreshURIToQueue() { + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); + if (!timer) { + continue; // this must be a nsRefreshURI already + } + + // Replace this timer object with a nsRefreshTimer object. + nsCOMPtr callback; + timer->GetCallback(getter_AddRefs(callback)); + + timer->Cancel(); + + mRefreshURIList->ReplaceElementAt(callback, i); + } + } +} + +NS_IMETHODIMP +nsDocShell::SuspendRefreshURIs() { + RefreshURIToQueue(); + + // Suspend refresh URIs for our child shells as well. + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->SuspendRefreshURIs(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ResumeRefreshURIs() { + RefreshURIFromQueue(); + + // Resume refresh URIs for our child shells as well. + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->ResumeRefreshURIs(); + } + } + + return NS_OK; +} + +nsresult nsDocShell::RefreshURIFromQueue() { + if (!mRefreshURIList) { + return NS_OK; + } + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + while (n) { + nsCOMPtr refreshInfo = + do_QueryElementAt(mRefreshURIList, --n); + + if (refreshInfo) { + // This is the nsRefreshTimer object, waiting to be + // setup in a timer object and fired. + // Create the timer and trigger it. + uint32_t delay = static_cast( + static_cast(refreshInfo)) + ->GetDelay(); + nsCOMPtr win = GetWindow(); + if (win) { + nsCOMPtr timer; + NS_NewTimerWithCallback(getter_AddRefs(timer), refreshInfo, delay, + nsITimer::TYPE_ONE_SHOT); + + if (timer) { + // Replace the nsRefreshTimer element in the queue with + // its corresponding timer object, so that in case another + // load comes through before the timer can go off, the timer will + // get cancelled in CancelRefreshURITimer() + mRefreshURIList->ReplaceElementAt(timer, n); + } + } + } + } + + return NS_OK; +} + +static bool IsFollowupPartOfMultipart(nsIRequest* aRequest) { + nsCOMPtr multiPartChannel = do_QueryInterface(aRequest); + bool firstPart = false; + return multiPartChannel && + NS_SUCCEEDED(multiPartChannel->GetIsFirstPart(&firstPart)) && + !firstPart; +} + +nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer, + WindowGlobalChild* aWindowActor, + bool aIsTransientAboutBlank, bool aPersist, + nsIRequest* aRequest, nsIURI* aPreviousURI) { + // Save the LayoutHistoryState of the previous document, before + // setting up new document + PersistLayoutHistoryState(); + + nsresult rv = SetupNewViewer(aContentViewer, aWindowActor); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX What if SetupNewViewer fails? + if (mozilla::SessionHistoryInParent() ? !!mLoadingEntry : !!mLSHE) { + // Set history.state + SetDocCurrentStateObj(mLSHE, + mLoadingEntry ? &mLoadingEntry->mInfo : nullptr); + } + + if (mLSHE) { + // Restore the editing state, if it's stored in session history. + if (mLSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mLSHE); + } + + SetHistoryEntryAndUpdateBC(Nothing(), Some(mLSHE)); + } + + if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent() && + !IsFollowupPartOfMultipart(aRequest)) { + bool expired = false; + uint32_t cacheKey = 0; + nsCOMPtr cacheChannel = do_QueryInterface(aRequest); + if (cacheChannel) { + // Check if the page has expired from cache + uint32_t expTime = 0; + cacheChannel->GetCacheTokenExpirationTime(&expTime); + uint32_t now = PRTimeToSeconds(PR_Now()); + if (expTime <= now) { + expired = true; + } + + // The checks for updating cache key are similar to the old session + // history in OnNewURI. Try to update the cache key if + // - we should update session history and aren't doing a session + // history load. + // - we're doing a forced reload. + if (((!mLoadingEntry || !mLoadingEntry->mLoadIsFromSessionHistory) && + mBrowsingContext->ShouldUpdateSessionHistory(mLoadType)) || + IsForceReloadType(mLoadType)) { + cacheChannel->GetCacheKey(&cacheKey); + } + } + + MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this)); + MoveLoadingToActiveEntry(aPersist, expired, cacheKey, aPreviousURI); + } + + bool updateHistory = true; + + // Determine if this type of load should update history + switch (mLoadType) { + case LOAD_NORMAL_REPLACE: + case LOAD_REFRESH_REPLACE: + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_REPLACE_BYPASS_CACHE: + updateHistory = false; + break; + default: + break; + } + + if (!updateHistory) { + SetLayoutHistoryState(nullptr); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::OnProgressChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) { + if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) { + // Save timing statistics. + nsCOMPtr channel(do_QueryInterface(aRequest)); + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString aURI; + uri->GetAsciiSpec(aURI); + + if (this == aProgress) { + mozilla::Unused << MaybeInitTiming(); + mTiming->NotifyFetchStart(uri, + ConvertLoadTypeToNavigationType(mLoadType)); + // If we are starting a DocumentChannel, we need to pass the timing + // statistics so that should a process switch occur, the starting type can + // be passed to the new DocShell running in the other content process. + if (RefPtr docChannel = do_QueryObject(aRequest)) { + docChannel->SetNavigationTiming(mTiming); + } + } + + // Page has begun to load + mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD); + + if ((aStateFlags & STATE_RESTORING) == 0) { + // Show the progress cursor if the pref is set + if (StaticPrefs::ui_use_activity_cursor()) { + nsCOMPtr mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(nsIWidget::Cursor{eCursor_spinning}); + } + } + + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + if (IsForceReloadType(mLoadType)) { + if (WindowContext* windowContext = + mBrowsingContext->GetCurrentWindowContext()) { + SessionStoreChild::From(windowContext->GetWindowGlobalChild()) + ->ResetSessionStore(mBrowsingContext, + mBrowsingContext->GetSessionStoreEpoch()); + } + } + } + } + } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { + // Page is loading + mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING); + } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) { + // Page has finished loading + mBusyFlags = BUSY_FLAGS_NONE; + + // Hide the progress cursor if the pref is set + if (StaticPrefs::ui_use_activity_cursor()) { + nsCOMPtr mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(nsIWidget::Cursor{eCursor_standard}); + } + } + } + + if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) { + nsCOMPtr webProgress = + do_QueryInterface(GetAsSupports(this)); + // Is the document stop notification for this document? + if (aProgress == webProgress.get()) { + nsCOMPtr channel(do_QueryInterface(aRequest)); + EndPageLoad(aProgress, channel, aStatus); + } + } + // note that redirect state changes will go through here as well, but it + // is better to handle those in OnRedirectStateChange where more + // information is available. + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + nsIURI* aURI, uint32_t aFlags) { + // Since we've now changed Documents, notify the BrowsingContext that we've + // changed. Ideally we'd just let the BrowsingContext do this when it + // changes the current window global, but that happens before this and we + // have a lot of tests that depend on the specific ordering of messages. + bool isTopLevel = false; + if (XRE_IsParentProcess() && + !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) && + NS_SUCCEEDED(aProgress->GetIsTopLevel(&isTopLevel)) && isTopLevel) { + GetBrowsingContext()->Canonical()->UpdateSecurityState(); + } + return NS_OK; +} + +void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aRedirectFlags, + uint32_t aStateFlags) { + NS_ASSERTION(aStateFlags & STATE_REDIRECTING, + "Calling OnRedirectStateChange when there is no redirect"); + + if (!(aStateFlags & STATE_IS_DOCUMENT)) { + return; // not a toplevel document + } + + nsCOMPtr oldURI, newURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + aNewChannel->GetURI(getter_AddRefs(newURI)); + if (!oldURI || !newURI) { + return; + } + + // DocumentChannel adds redirect chain to global history in the parent + // process. The redirect chain can't be queried from the content process, so + // there's no need to update global history here. + RefPtr docChannel = do_QueryObject(aOldChannel); + if (!docChannel) { + // Below a URI visit is saved (see AddURIVisit method doc). + // The visit chain looks something like: + // ... + // Site N - 1 + // => Site N + // (redirect to =>) Site N + 1 (we are here!) + + // Get N - 1 and transition type + nsCOMPtr previousURI; + uint32_t previousFlags = 0; + ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags); + + if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL || + net::ChannelIsPost(aOldChannel)) { + // 1. Internal redirects are ignored because they are specific to the + // channel implementation. + // 2. POSTs are not saved by global history. + // + // Regardless, we need to propagate the previous visit to the new + // channel. + SaveLastVisit(aNewChannel, previousURI, previousFlags); + } else { + // Get the HTTP response code, if available. + uint32_t responseStatus = 0; + nsCOMPtr httpChannel = do_QueryInterface(aOldChannel); + if (httpChannel) { + Unused << httpChannel->GetResponseStatus(&responseStatus); + } + + // Add visit N -1 => N + AddURIVisit(oldURI, previousURI, previousFlags, responseStatus); + + // Since N + 1 could be the final destination, we will not save N => N + 1 + // here. OnNewURI will do that, so we will cache it. + SaveLastVisit(aNewChannel, oldURI, aRedirectFlags); + } + } + + if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && + mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); + } +} + +NS_IMETHODIMP +nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aState) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aEvent) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +already_AddRefed nsDocShell::KeywordToURI( + const nsACString& aKeyword, bool aIsPrivateContext) { + nsCOMPtr info; + if (!XRE_IsContentProcess()) { + nsCOMPtr uriFixup = components::URIFixup::Service(); + if (uriFixup) { + uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info)); + } + } + return info.forget(); +} + +/* static */ +already_AddRefed nsDocShell::MaybeFixBadCertDomainErrorURI( + nsIChannel* aChannel, nsIURI* aUrl) { + if (!aChannel) { + return nullptr; + } + + nsresult rv = NS_OK; + nsAutoCString host; + rv = aUrl->GetAsciiHost(host); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // No point in going further if "www." is included in the hostname + // already. That is the only hueristic we're applying in this function. + if (StringBeginsWith(host, "www."_ns)) { + return nullptr; + } + + // Return if fixup enable pref is turned off. + if (!mozilla::StaticPrefs::security_bad_cert_domain_error_url_fix_enabled()) { + return nullptr; + } + + // Return if scheme is not HTTPS. + if (!SchemeIsHTTPS(aUrl)) { + return nullptr; + } + + nsCOMPtr info = aChannel->LoadInfo(); + if (!info) { + return nullptr; + } + + // Skip doing the fixup if our channel was redirected, because we + // shouldn't be guessing things about the post-redirect URI. + if (!info->RedirectChain().IsEmpty()) { + return nullptr; + } + + int32_t port = 0; + rv = aUrl->GetPort(&port); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Don't fix up hosts with ports. + if (port != -1) { + return nullptr; + } + + // Don't fix up localhost url. + if (host == "localhost") { + return nullptr; + } + + // Don't fix up hostnames with IP address. + if (net_IsValidIPv4Addr(host) || net_IsValidIPv6Addr(host)) { + return nullptr; + } + + nsAutoCString userPass; + rv = aUrl->GetUserPass(userPass); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Security - URLs with user / password info should NOT be modified. + if (!userPass.IsEmpty()) { + return nullptr; + } + + nsCOMPtr tsi; + rv = aChannel->GetSecurityInfo(getter_AddRefs(tsi)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + if (NS_WARN_IF(!tsi)) { + return nullptr; + } + + nsCOMPtr cert; + rv = tsi->GetServerCert(getter_AddRefs(cert)); + if (NS_WARN_IF(NS_FAILED(rv) || !cert)) { + return nullptr; + } + + nsTArray certBytes; + rv = cert->GetRawDER(certBytes); + if (NS_FAILED(rv)) { + return nullptr; + } + + mozilla::pkix::Input serverCertInput; + mozilla::pkix::Result rv1 = + serverCertInput.Init(certBytes.Elements(), certBytes.Length()); + if (rv1 != mozilla::pkix::Success) { + return nullptr; + } + + nsAutoCString newHost("www."_ns); + newHost.Append(host); + + mozilla::pkix::Input newHostInput; + rv1 = newHostInput.Init( + BitwiseCast(newHost.BeginReading()), + newHost.Length()); + if (rv1 != mozilla::pkix::Success) { + return nullptr; + } + + // Check if adding a "www." prefix to the request's hostname will + // cause the response's certificate to match. + rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput); + if (rv1 != mozilla::pkix::Success) { + return nullptr; + } + + nsCOMPtr newURI; + Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize( + getter_AddRefs(newURI)); + + return newURI.forget(); +} + +/* static */ +already_AddRefed nsDocShell::AttemptURIFixup( + nsIChannel* aChannel, nsresult aStatus, + const mozilla::Maybe& aOriginalURIString, uint32_t aLoadType, + bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing, + bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData) { + if (aStatus != NS_ERROR_UNKNOWN_HOST && aStatus != NS_ERROR_NET_RESET && + aStatus != NS_ERROR_CONNECTION_REFUSED && + aStatus != + mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { + return nullptr; + } + + if (!(aLoadType == LOAD_NORMAL && aIsTopFrame) && !aAllowKeywordFixup) { + return nullptr; + } + + nsCOMPtr url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return nullptr; + } + + // + // Try and make an alternative URI from the old one + // + nsCOMPtr newURI; + nsCOMPtr newPostData; + + nsAutoCString oldSpec; + url->GetSpec(oldSpec); + + // + // First try keyword fixup + // + nsAutoString keywordProviderName, keywordAsSent; + if (aStatus == NS_ERROR_UNKNOWN_HOST && aAllowKeywordFixup) { + // we should only perform a keyword search under the following + // conditions: + // (0) Pref keyword.enabled is true + // (1) the url scheme is http (or https) + // (2) the url does not have a protocol scheme + // If we don't enforce such a policy, then we end up doing + // keyword searchs on urls we don't intend like imap, file, + // mailbox, etc. This could lead to a security problem where we + // send data to the keyword server that we shouldn't be. + // Someone needs to clean up keywords in general so we can + // determine on a per url basis if we want keywords + // enabled...this is just a bandaid... + nsAutoCString scheme; + Unused << url->GetScheme(scheme); + if (Preferences::GetBool("keyword.enabled", false) && + StringBeginsWith(scheme, "http"_ns)) { + bool attemptFixup = false; + nsAutoCString host; + Unused << url->GetHost(host); + if (host.FindChar('.') == kNotFound) { + attemptFixup = true; + } else { + // For domains with dots, we check the public suffix validity. + nsCOMPtr tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + if (tldService) { + nsAutoCString suffix; + attemptFixup = + NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) && + suffix.IsEmpty(); + } + } + if (attemptFixup) { + nsCOMPtr info; + // only send non-qualified hosts to the keyword server + if (aOriginalURIString && !aOriginalURIString->IsEmpty()) { + info = KeywordToURI(*aOriginalURIString, aUsePrivateBrowsing); + } else { + // + // If this string was passed through nsStandardURL by + // chance, then it may have been converted from UTF-8 to + // ACE, which would result in a completely bogus keyword + // query. Here we try to recover the original Unicode + // value, but this is not 100% correct since the value may + // have been normalized per the IDN normalization rules. + // + // Since we don't have access to the exact original string + // that was entered by the user, this will just have to do. + bool isACE; + nsAutoCString utf8Host; + nsCOMPtr idnSrv = + do_GetService(NS_IDNSERVICE_CONTRACTID); + if (idnSrv && NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE && + NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) { + info = KeywordToURI(utf8Host, aUsePrivateBrowsing); + + } else { + info = KeywordToURI(host, aUsePrivateBrowsing); + } + } + if (info) { + info->GetPreferredURI(getter_AddRefs(newURI)); + if (newURI) { + info->GetKeywordAsSent(keywordAsSent); + info->GetKeywordProviderName(keywordProviderName); + info->GetPostData(getter_AddRefs(newPostData)); + } + } + } + } + } + + // + // Now try change the address, e.g. turn http://foo into + // http://www.foo.com, and if that doesn't work try https with + // https://foo and https://www.foo.com. + // + if (aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET) { + // Skip fixup for anything except a normal document load + // operation on the topframe. + bool doCreateAlternate = aLoadType == LOAD_NORMAL && aIsTopFrame; + + if (doCreateAlternate) { + nsCOMPtr loadInfo = aChannel->LoadInfo(); + nsIPrincipal* principal = loadInfo->TriggeringPrincipal(); + // Only do this if our channel was loaded directly by the user from the + // URL bar or similar (system principal) and not redirected, because we + // shouldn't be guessing things about links from other sites, or a + // post-redirect URI. + doCreateAlternate = principal && principal->IsSystemPrincipal() && + loadInfo->RedirectChain().IsEmpty(); + } + // Test if keyword lookup produced a new URI or not + if (doCreateAlternate && newURI) { + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + // Keyword lookup made a new URI so no need to try + // an alternate one. + doCreateAlternate = false; + } + } + if (doCreateAlternate) { + newURI = nullptr; + newPostData = nullptr; + keywordProviderName.Truncate(); + keywordAsSent.Truncate(); + nsCOMPtr uriFixup = components::URIFixup::Service(); + if (uriFixup) { + nsCOMPtr fixupInfo; + uriFixup->GetFixupURIInfo(oldSpec, + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, + getter_AddRefs(fixupInfo)); + if (fixupInfo) { + fixupInfo->GetPreferredURI(getter_AddRefs(newURI)); + } + } + } + } else if (aStatus == NS_ERROR_CONNECTION_REFUSED && + Preferences::GetBool("browser.fixup.fallback-to-https", false)) { + // Try HTTPS, since http didn't work + if (SchemeIsHTTP(url)) { + int32_t port = 0; + url->GetPort(&port); + + // Fall back to HTTPS only if port is default + if (port == -1) { + newURI = nullptr; + newPostData = nullptr; + Unused << NS_MutateURI(url) + .SetScheme("https"_ns) + .Finalize(getter_AddRefs(newURI)); + } + } + } + + // If we have a SSL_ERROR_BAD_CERT_DOMAIN error, try prefixing the domain name + // with www. to see if we can avoid showing the cert error page. For example, + // https://example.com -> https://www.example.com. + if (aStatus == + mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { + newPostData = nullptr; + newURI = MaybeFixBadCertDomainErrorURI(aChannel, url); + } + + // Did we make a new URI that is different to the old one? If so + // load it. + // + if (newURI) { + // Make sure the new URI is different from the old one, + // otherwise there's little point trying to load it again. + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + if (aNewPostData) { + newPostData.forget(aNewPostData); + } + if (aNotifyKeywordSearchLoading) { + // This notification is meant for Firefox Health Report so it + // can increment counts from the search engine + MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent); + } + return newURI.forget(); + } + } + + return nullptr; +} + +nsresult nsDocShell::FilterStatusForErrorPage( + nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType, + bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument, + bool* aSkippedUnknownProtocolNavigation) { + // Errors to be shown only on top-level frames + if ((aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_CONNECTION_REFUSED || + aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || + aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED || + aStatus == NS_ERROR_PROXY_FORBIDDEN || + aStatus == NS_ERROR_PROXY_NOT_IMPLEMENTED || + aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED || + aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS || + aStatus == NS_ERROR_MALFORMED_URI || + aStatus == NS_ERROR_BLOCKED_BY_POLICY || + aStatus == NS_ERROR_DOM_COOP_FAILED || + aStatus == NS_ERROR_DOM_COEP_FAILED) && + (aIsTopFrame || aUseErrorPages)) { + return aStatus; + } + + if (aStatus == NS_ERROR_NET_TIMEOUT || + aStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL || + aStatus == NS_ERROR_PROXY_GATEWAY_TIMEOUT || + aStatus == NS_ERROR_REDIRECT_LOOP || + aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || + aStatus == NS_ERROR_NET_INTERRUPT || aStatus == NS_ERROR_NET_RESET || + aStatus == NS_ERROR_PROXY_BAD_GATEWAY || aStatus == NS_ERROR_OFFLINE || + aStatus == NS_ERROR_MALWARE_URI || aStatus == NS_ERROR_PHISHING_URI || + aStatus == NS_ERROR_UNWANTED_URI || aStatus == NS_ERROR_HARMFUL_URI || + aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || + aStatus == NS_ERROR_INTERCEPTION_FAILED || + aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || + aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY || + aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR || + aStatus == NS_ERROR_DOM_BAD_URI || aStatus == NS_ERROR_FILE_NOT_FOUND || + aStatus == NS_ERROR_FILE_ACCESS_DENIED || + aStatus == NS_ERROR_CORRUPTED_CONTENT || + aStatus == NS_ERROR_INVALID_CONTENT_ENCODING || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { + // Errors to be shown for any frame + return aStatus; + } + + if (aStatus == NS_ERROR_UNKNOWN_PROTOCOL) { + // For unknown protocols we only display an error if the load is triggered + // by the browser itself, or we're replacing the initial document (and + // nothing else). Showing the error for page-triggered navigations causes + // annoying behavior for users, see bug 1528305. + // + // We could, maybe, try to detect if this is in response to some user + // interaction (like clicking a link, or something else) and maybe show + // the error page in that case. But this allows for ctrl+clicking and such + // to see the error page. + nsCOMPtr info = aChannel->LoadInfo(); + if (!info->TriggeringPrincipal()->IsSystemPrincipal() && + StaticPrefs::dom_no_unknown_protocol_error_enabled() && + !aIsInitialDocument) { + if (aSkippedUnknownProtocolNavigation) { + *aSkippedUnknownProtocolNavigation = true; + } + return NS_OK; + } + return aStatus; + } + + if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) { + // Non-caching channels will simply return NS_ERROR_OFFLINE. + // Caching channels would have to look at their flags to work + // out which error to return. Or we can fix up the error here. + if (!(aLoadType & LOAD_CMD_HISTORY)) { + return NS_ERROR_OFFLINE; + } + return aStatus; + } + + return NS_OK; +} + +nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress, + nsIChannel* aChannel, nsresult aStatus) { + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p EndPageLoad status: %" PRIx32 "\n", this, + static_cast(aStatus))); + if (!aChannel) { + return NS_ERROR_NULL_POINTER; + } + + // Make sure to discard the initial client if we never created the initial + // about:blank document. Do this before possibly returning from the method + // due to an error. + mInitialClientSource.reset(); + + nsCOMPtr reporter = do_QueryInterface(aChannel); + if (reporter) { + nsCOMPtr loadGroup; + aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + reporter->FlushConsoleReports(loadGroup); + } else { + reporter->FlushConsoleReports(GetDocument()); + } + } + + nsCOMPtr url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr timingChannel = do_QueryInterface(aChannel); + if (timingChannel) { + TimeStamp channelCreationTime; + rv = timingChannel->GetChannelCreation(&channelCreationTime); + if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, + channelCreationTime); + } + } + + // Timing is picked up by the window, we don't need it anymore + mTiming = nullptr; + + // clean up reload state for meta charset + if (eCharsetReloadRequested == mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadStopOrigional; + } else { + mCharsetReloadState = eCharsetReloadInit; + } + + // Save a pointer to the currently-loading history entry. + // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history + // entry further down in this method. + nsCOMPtr loadingSHE = mLSHE; + mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore + + // + // one of many safeguards that prevent death and destruction if + // someone is so very very rude as to bring this window down + // during this load handler. + // + nsCOMPtr kungFuDeathGrip(this); + + // Notify the ContentViewer that the Document has finished loading. This + // will cause any OnLoad(...) and PopState(...) handlers to fire. + if (!mEODForCurrentDocument && mContentViewer) { + mIsExecutingOnLoadHandler = true; + nsCOMPtr contentViewer = mContentViewer; + contentViewer->LoadComplete(aStatus); + mIsExecutingOnLoadHandler = false; + + mEODForCurrentDocument = true; + } + /* Check if the httpChannel has any cache-control related response headers, + * like no-store, no-cache. If so, update SHEntry so that + * when a user goes back/forward to this page, we appropriately do + * form value restoration or load from server. + */ + nsCOMPtr httpChannel(do_QueryInterface(aChannel)); + if (!httpChannel) { + // HttpChannel could be hiding underneath a Multipart channel. + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + // figure out if SH should be saving layout state. + bool discardLayoutState = ShouldDiscardLayoutState(httpChannel); + if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) && + (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) { + mLSHE->SetSaveLayoutStateFlag(false); + } + } + + // Clear mLSHE after calling the onLoadHandlers. This way, if the + // onLoadHandler tries to load something different in + // itself or one of its children, we can deal with it appropriately. + if (mLSHE) { + mLSHE->SetLoadType(LOAD_HISTORY); + + // Clear the mLSHE reference to indicate document loading is done one + // way or another. + SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); + } + mActiveEntryIsLoadingFromSessionHistory = false; + + // if there's a refresh header in the channel, this method + // will set it up for us. + if (mBrowsingContext->IsActive() || !mDisableMetaRefreshWhenInactive) + RefreshURIFromQueue(); + + // Test whether this is the top frame or a subframe + bool isTopFrame = mBrowsingContext->IsTop(); + + bool hadErrorStatus = false; + // If status code indicates an error it means that DocumentChannel already + // tried to fixup the uri and failed. Throw an error dialog box here. + if (NS_FAILED(aStatus)) { + // If we got CONTENT_BLOCKED from EndPageLoad, then we need to fire + // the error event to our embedder, since tests are relying on this. + // The error event is usually fired by the caller of InternalLoad, but + // this particular error can happen asynchronously. + // Bug 1629201 is filed for having much clearer decision making around + // which cases need error events. + bool fireFrameErrorEvent = (aStatus == NS_ERROR_CONTENT_BLOCKED_SHOW_ALT || + aStatus == NS_ERROR_CONTENT_BLOCKED); + UnblockEmbedderLoadEventForFailure(fireFrameErrorEvent); + + bool isInitialDocument = + !GetExtantDocument() || GetExtantDocument()->IsInitialDocument(); + bool skippedUnknownProtocolNavigation = false; + aStatus = FilterStatusForErrorPage(aStatus, aChannel, mLoadType, isTopFrame, + mBrowsingContext->GetUseErrorPages(), + isInitialDocument, + &skippedUnknownProtocolNavigation); + hadErrorStatus = true; + if (NS_FAILED(aStatus)) { + if (!mIsBeingDestroyed) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + } + } else if (skippedUnknownProtocolNavigation) { + nsTArray params; + if (NS_FAILED( + NS_GetSanitizedURIStringFromURI(url, *params.AppendElement()))) { + params.LastElement().AssignLiteral(u"(unknown uri)"); + } + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM"_ns, GetExtantDocument(), + nsContentUtils::eDOM_PROPERTIES, "UnknownProtocolNavigationPrevented", + params); + } + } else { + // If we have a host + nsCOMPtr loadInfo = aChannel->LoadInfo(); + PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes()); + } + + if (hadErrorStatus) { + // Don't send session store updates if the reason EndPageLoad was called is + // because we are process switching. Sometimes the update takes too long and + // incorrectly overrides session store data from the following load. + return NS_OK; + } + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + if (WindowContext* windowContext = + mBrowsingContext->GetCurrentWindowContext()) { + using Change = SessionStoreChangeListener::Change; + + // We've finished loading the page and now we want to collect all the + // session store state that the page is initialized with. + SessionStoreChangeListener::CollectSessionStoreData( + windowContext, + EnumSet(Change::Input, Change::Scroll, Change::SessionHistory, + Change::WireFrame)); + } + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell: Content Viewer Management +//***************************************************************************** + +nsresult nsDocShell::EnsureContentViewer() { + if (mContentViewer) { + return NS_OK; + } + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr cspToInheritForAboutBlank; + nsCOMPtr baseURI; + nsIPrincipal* principal = GetInheritedPrincipal(false); + nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true); + + nsCOMPtr parentItem; + GetInProcessSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + if (nsCOMPtr domWin = GetWindow()) { + nsCOMPtr parentElement = domWin->GetFrameElementInternal(); + if (parentElement) { + baseURI = parentElement->GetBaseURI(); + cspToInheritForAboutBlank = parentElement->GetCsp(); + } + } + } + + nsresult rv = CreateAboutBlankContentViewer( + principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI, + /* aIsInitialDocument */ true); + + NS_ENSURE_STATE(mContentViewer); + + if (NS_SUCCEEDED(rv)) { + RefPtr doc(GetDocument()); + MOZ_ASSERT(doc, + "Should have doc if CreateAboutBlankContentViewer " + "succeeded!"); + MOZ_ASSERT(doc->IsInitialDocument(), "Document should be initial document"); + + // Documents created using EnsureContentViewer may be transient + // placeholders created by framescripts before content has a + // chance to load. In some cases, window.open(..., "noopener") + // will create such a document and then synchronously tear it + // down, firing a "pagehide" event. Doing so violates our + // assertions about DocGroups. It's easier to silence the + // assertion here than to avoid creating the extra document. + doc->IgnoreDocGroupMismatches(); + } + + return rv; +} + +nsresult nsDocShell::CreateAboutBlankContentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, bool aIsInitialDocument, + const Maybe& aCOEP, + bool aTryToSaveOldPresentation, bool aCheckPermitUnload, + WindowGlobalChild* aActor) { + RefPtr blankDoc; + nsCOMPtr viewer; + nsresult rv = NS_ERROR_FAILURE; + + MOZ_ASSERT_IF(aActor, aActor->DocumentPrincipal() == aPrincipal); + + /* mCreatingDocument should never be true at this point. However, it's + a theoretical possibility. We want to know about it and make it stop, + and this sounds like a job for an assertion. */ + NS_ASSERTION(!mCreatingDocument, + "infinite(?) loop creating document averted"); + if (mCreatingDocument) { + return NS_ERROR_FAILURE; + } + + if (!mBrowsingContext->AncestorsAreCurrent() || + mBrowsingContext->IsInBFCache()) { + mBrowsingContext->RemoveRootFromBFCacheSync(); + return NS_ERROR_NOT_AVAILABLE; + } + + // mContentViewer->PermitUnload may release |this| docshell. + nsCOMPtr kungFuDeathGrip(this); + + AutoRestore creatingDocument(mCreatingDocument); + mCreatingDocument = true; + + if (aPrincipal && !aPrincipal->IsSystemPrincipal() && + mItemType != typeChrome) { + MOZ_ASSERT(aPrincipal->OriginAttributesRef() == + mBrowsingContext->OriginAttributesRef()); + } + + // Make sure timing is created. But first record whether we had it + // already, so we don't clobber the timing for an in-progress load. + bool hadTiming = mTiming; + bool toBeReset = MaybeInitTiming(); + if (mContentViewer) { + if (aCheckPermitUnload) { + // We've got a content viewer already. Make sure the user + // permits us to discard the current document and replace it + // with about:blank. And also ensure we fire the unload events + // in the current document. + + // Unload gets fired first for + // document loaded from the session history. + mTiming->NotifyBeforeUnload(); + + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the load. + MaybeResetInitTiming(toBeReset); + return NS_ERROR_FAILURE; + } + if (mTiming) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + } + + mSavingOldViewer = + aTryToSaveOldPresentation && + CanSavePresentation(LOAD_NORMAL, nullptr, nullptr, + /* aReportBFCacheComboTelemetry */ true); + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Stop any in-progress loading, so that we don't accidentally trigger any + // PageShow notifications from Embed() interrupting our loading below. + Stop(); + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + (void)FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + } + + // Now make sure we don't think we're in the middle of firing unload after + // this point. This will make us fire unload when the about:blank document + // unloads... but that's ok, more or less. Would be nice if it fired load + // too, of course. + mFiredUnloadEvent = false; + + nsCOMPtr docFactory = + nsContentUtils::FindInternalContentViewer("text/html"_ns); + + if (docFactory) { + nsCOMPtr principal, partitionedPrincipal; + const uint32_t sandboxFlags = + mBrowsingContext->GetHasLoadedNonInitialDocument() + ? mBrowsingContext->GetSandboxFlags() + : mBrowsingContext->GetInitialSandboxFlags(); + // If we're sandboxed, then create a new null principal. We skip + // this if we're being created from WindowGlobalChild, since in + // that case we already have a null principal if required. + // We can't compare againt the BrowsingContext sandbox flag, since + // the value was taken when the load initiated and may have since + // changed. + if ((sandboxFlags & SANDBOXED_ORIGIN) && !aActor) { + if (aPrincipal) { + principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal); + } else { + principal = NullPrincipal::Create(GetOriginAttributes()); + } + partitionedPrincipal = principal; + } else { + principal = aPrincipal; + partitionedPrincipal = aPartitionedPrincipal; + } + + // We cannot get the foreign partitioned prinicpal for the initial + // about:blank page. So, we change to check if we need to use the + // partitioned principal for the service worker here. + MaybeCreateInitialClientSource( + StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker( + this) + ? partitionedPrincipal + : principal); + + // generate (about:blank) document to load + blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal, + partitionedPrincipal, this); + if (blankDoc) { + // Hack: manually set the CSP for the new document + // Please create an actual copy of the CSP (do not share the same + // reference) otherwise appending a new policy within the new + // document will be incorrectly propagated to the opening doc. + if (aCSP) { + RefPtr cspToInherit = new nsCSPContext(); + cspToInherit->InitFromOther(static_cast(aCSP)); + blankDoc->SetCsp(cspToInherit); + } + + blankDoc->SetIsInitialDocument(aIsInitialDocument); + + blankDoc->SetEmbedderPolicy(aCOEP); + + // Hack: set the base URI manually, since this document never + // got Reset() with a channel. + blankDoc->SetBaseURI(aBaseURI); + + // Copy our sandbox flags to the document. These are immutable + // after being set here. + blankDoc->SetSandboxFlags(sandboxFlags); + + blankDoc->InitFeaturePolicy(); + + // create a content viewer for us and the new document + docFactory->CreateInstanceForDocument( + NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view", + getter_AddRefs(viewer)); + + // hook 'em up + if (viewer) { + viewer->SetContainer(this); + rv = Embed(viewer, aActor, true, false, nullptr, mCurrentURI); + NS_ENSURE_SUCCESS(rv, rv); + + SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, + /* aFireLocationChange */ true, + /* aIsInitialAboutBlank */ true, + /* aLocationFlags */ 0); + rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; + } + } + } + + // The transient about:blank viewer doesn't have a session history entry. + SetHistoryEntryAndUpdateBC(Nothing(), Some(nullptr)); + + // Clear out our mTiming like we would in EndPageLoad, if we didn't + // have one before entering this function. + if (!hadTiming) { + mTiming = nullptr; + mBlankTiming = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, + nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP) { + return CreateAboutBlankContentViewer(aPrincipal, aPartitionedPrincipal, aCSP, + nullptr, /* aIsInitialDocument */ false); +} + +nsresult nsDocShell::CreateContentViewerForActor( + WindowGlobalChild* aWindowActor) { + MOZ_ASSERT(aWindowActor); + + // FIXME: WindowGlobalChild should provide the PartitionedPrincipal. + // FIXME: We may want to support non-initial documents here. + nsresult rv = CreateAboutBlankContentViewer( + aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(), + /* aCsp */ nullptr, + /* aBaseURI */ nullptr, + /* aIsInitialDocument */ true, + /* aCOEP */ Nothing(), + /* aTryToSaveOldPresentation */ true, + /* aCheckPermitUnload */ true, aWindowActor); +#ifdef DEBUG + if (NS_SUCCEEDED(rv)) { + RefPtr doc(GetDocument()); + MOZ_ASSERT( + doc, + "Should have a document if CreateAboutBlankContentViewer succeeded"); + MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(), + "New document should be in the same global as our actor"); + MOZ_ASSERT(doc->IsInitialDocument(), + "New document should be an initial document"); + } +#endif + + return rv; +} + +bool nsDocShell::CanSavePresentation(uint32_t aLoadType, + nsIRequest* aNewRequest, + Document* aNewDocument, + bool aReportBFCacheComboTelemetry) { + if (!mOSHE) { + return false; // no entry to save into + } + + MOZ_ASSERT(!mozilla::SessionHistoryInParent(), + "mOSHE cannot be non-null with SHIP"); + nsCOMPtr viewer = mOSHE->GetContentViewer(); + if (viewer) { + NS_WARNING("mOSHE already has a content viewer!"); + return false; + } + + // Only save presentation for "normal" loads and link loads. Anything else + // probably wants to refetch the page, so caching the old presentation + // would be incorrect. + if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY && + aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT && + aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && + aLoadType != LOAD_ERROR_PAGE) { + return false; + } + + // If the session history entry has the saveLayoutState flag set to false, + // then we should not cache the presentation. + if (!mOSHE->GetSaveLayoutStateFlag()) { + return false; + } + + // If the document is not done loading, don't cache it. + if (!mScriptGlobal || mScriptGlobal->IsLoading()) { + MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, + ("Blocked due to document still loading")); + return false; + } + + if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) { + return false; + } + + // Avoid doing the work of saving the presentation state in the case where + // the content viewer cache is disabled. + if (nsSHistory::GetMaxTotalViewers() == 0) { + return false; + } + + // Don't cache the content viewer if we're in a subframe. + if (mBrowsingContext->GetParent()) { + return false; // this is a subframe load + } + + // If the document does not want its presentation cached, then don't. + RefPtr doc = mScriptGlobal->GetExtantDoc(); + + uint32_t bfCacheCombo = 0; + bool canSavePresentation = + doc->CanSavePresentation(aNewRequest, bfCacheCombo, true); + MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0); + if (canSavePresentation && doc->IsTopLevelContentDocument()) { + auto* browsingContextGroup = mBrowsingContext->Group(); + nsTArray>& topLevelContext = + browsingContextGroup->Toplevels(); + + for (const auto& browsingContext : topLevelContext) { + if (browsingContext != mBrowsingContext) { + if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { + canSavePresentation = false; + } + bfCacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG; + break; + } + } + } + + if (aReportBFCacheComboTelemetry) { + ReportBFCacheComboTelemetry(bfCacheCombo); + } + return doc && canSavePresentation; +} + +/* static */ +void nsDocShell::ReportBFCacheComboTelemetry(uint32_t aCombo) { + // There are 11 possible reasons to make a request fails to use BFCache + // (see BFCacheStatus in dom/base/Document.h), and we'd like to record + // the common combinations for reasons which make requests fail to use + // BFCache. These combinations are generated based on some local browsings, + // we need to adjust them when necessary. + enum BFCacheStatusCombo : uint32_t { + BFCACHE_SUCCESS, + NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG, + // If both unload and beforeunload listeners are presented, it'll be + // recorded as unload + UNLOAD = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER, + UNLOAD_REQUEST = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST, + REQUEST = mozilla::dom::BFCacheStatus::REQUEST, + UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, + UNLOAD_REQUEST_PEER_MSE = + mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION | + mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, + UNLOAD_REQUEST_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, + SUSPENDED_UNLOAD_REQUEST_PEER = + mozilla::dom::BFCacheStatus::SUSPENDED | + mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, + REMOTE_SUBFRAMES = mozilla::dom::BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES, + BEFOREUNLOAD = mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER, + }; + + // Beforeunload is recorded as a blocker only if it is the only one to block + // bfcache. + if (aCombo != mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER) { + aCombo &= ~mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER; + } + switch (aCombo) { + case BFCACHE_SUCCESS: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); + break; + case NOT_ONLY_TOPLEVEL: + if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Other); + break; + } + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Success_Not_Toplevel); + break; + case UNLOAD: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Unload); + break; + case BEFOREUNLOAD: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Beforeunload); + break; + case UNLOAD_REQUEST: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req); + break; + case REQUEST: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Req); + break; + case UNLOAD_REQUEST_PEER: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer); + break; + case UNLOAD_REQUEST_PEER_MSE: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer_MSE); + break; + case UNLOAD_REQUEST_MSE: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_MSE); + break; + case SUSPENDED_UNLOAD_REQUEST_PEER: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::SPD_Unload_Req_Peer); + break; + case REMOTE_SUBFRAMES: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Remote_Subframes); + break; + default: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Other); + break; + } +}; + +void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ASSERTION(!mEditorData, + "Why reattach an editor when we already have one?"); + NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), + "Reattaching when there's not a detached editor."); + + if (mEditorData || !aSHEntry) { + return; + } + + mEditorData = WrapUnique(aSHEntry->ForgetEditorData()); + if (mEditorData) { +#ifdef DEBUG + nsresult rv = +#endif + mEditorData->ReattachToWindow(this); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); + } +} + +void nsDocShell::DetachEditorFromWindow() { + if (!mEditorData || mEditorData->WaitingForLoad()) { + // If there's nothing to detach, or if the editor data is actually set + // up for the _new_ page that's coming in, don't detach. + return; + } + + NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), + "Detaching editor when it's already detached."); + + nsresult res = mEditorData->DetachFromWindow(); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); + + if (NS_SUCCEEDED(res)) { + // Make mOSHE hold the owning ref to the editor data. + if (mOSHE) { + MOZ_ASSERT(!mIsBeingDestroyed || !mOSHE->HasDetachedEditor(), + "We should not set the editor data again once after we " + "detached the editor data during destroying this docshell"); + mOSHE->SetEditorData(mEditorData.release()); + } else { + mEditorData = nullptr; + } + } + +#ifdef DEBUG + { + bool isEditable; + GetEditable(&isEditable); + NS_ASSERTION(!isEditable, + "Window is still editable after detaching editor."); + } +#endif // DEBUG +} + +nsresult nsDocShell::CaptureState() { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + + if (!mOSHE || mOSHE == mLSHE) { + // No entry to save into, or we're replacing the existing entry. + return NS_ERROR_FAILURE; + } + + if (!mScriptGlobal) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr windowState = mScriptGlobal->SaveWindowState(); + NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { + nsAutoCString spec; + nsCOMPtr uri = mOSHE->GetURI(); + if (uri) { + uri->GetSpec(spec); + } + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("Saving presentation into session history, URI: %s", spec.get())); + } + + mOSHE->SetWindowState(windowState); + + // Suspend refresh URIs and save off the timer queue + mOSHE->SetRefreshURIList(mSavedRefreshURIList); + + // Capture the current content viewer bounds. + if (mContentViewer) { + nsIntRect bounds; + mContentViewer->GetBounds(bounds); + mOSHE->SetViewerBounds(bounds); + } + + // Capture the docshell hierarchy. + mOSHE->ClearChildShells(); + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr childShell = do_QueryInterface(ChildAt(i)); + NS_ASSERTION(childShell, "null child shell"); + + mOSHE->AddChildShell(childShell); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RestorePresentationEvent::Run() { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + + if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) { + NS_WARNING("RestoreFromHistory failed"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + + nsresult rv; + if (!aContentViewer) { + rv = EnsureContentViewer(); + NS_ENSURE_SUCCESS(rv, rv); + + aContentViewer = mContentViewer; + } + + // Dispatch events for restoring the presentation. We try to simulate + // the progress notifications loading the document would cause, so we add + // the document's channel to the loadgroup to initiate stateChange + // notifications. + + RefPtr doc = aContentViewer->GetDocument(); + if (doc) { + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + mIsRestoringDocument = false; + } + } + + if (!aTop) { + // This point corresponds to us having gotten OnStartRequest or + // STATE_START, so do the same thing that CreateContentViewer does at + // this point to ensure that unload/pagehide events for this document + // will fire when it's unloaded again. + mFiredUnloadEvent = false; + + // For non-top frames, there is no notion of making sure that the + // previous document is in the domwindow when STATE_START notifications + // happen. We can just call BeginRestore for all of the child shells + // now. + rv = BeginRestoreChildren(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult nsDocShell::BeginRestoreChildren() { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + nsresult rv = child->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FinishRestore() { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + + // First we call finishRestore() on our children. In the simulated load, + // all of the child frames finish loading before the main document. + + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + child->FinishRestore(); + } + } + + if (mOSHE && mOSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mOSHE); + } + + RefPtr doc = GetDocument(); + if (doc) { + // Finally, we remove the request from the loadgroup. This will + // cause onStateChange(STATE_STOP) to fire, which will fire the + // pageshow event to the chrome. + + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mIsRestoringDocument = true; + mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); + mIsRestoringDocument = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRestoringDocument(bool* aRestoring) { + *aRestoring = mIsRestoringDocument; + return NS_OK; +} + +nsresult nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, + bool* aRestoring) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, + "RestorePresentation should only be called for history loads"); + + nsCOMPtr viewer = aSHEntry->GetContentViewer(); + + nsAutoCString spec; + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { + nsCOMPtr uri = aSHEntry->GetURI(); + if (uri) { + uri->GetSpec(spec); + } + } + + *aRestoring = false; + + if (!viewer) { + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("no saved presentation for uri: %s", spec.get())); + return NS_OK; + } + + // We need to make sure the content viewer's container is this docshell. + // In subframe navigation, it's possible for the docshell that the + // content viewer was originally loaded into to be replaced with a + // different one. We don't currently support restoring the presentation + // in that case. + + nsCOMPtr container; + viewer->GetContainer(getter_AddRefs(container)); + if (!::SameCOMIdentity(container, GetAsSupports(this))) { + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("No valid container, clearing presentation")); + aSHEntry->SetContentViewer(nullptr); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); + + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("restoring presentation from session history: %s", spec.get())); + + SetHistoryEntryAndUpdateBC(Some(aSHEntry), Nothing()); + + // Post an event that will remove the request after we've returned + // to the event loop. This mimics the way it is called by nsIChannel + // implementations. + + // Revoke any pending restore (just in case). + NS_ASSERTION(!mRestorePresentationEvent.IsPending(), + "should only have one RestorePresentationEvent"); + mRestorePresentationEvent.Revoke(); + + RefPtr evt = new RestorePresentationEvent(this); + nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(evt)); + if (NS_SUCCEEDED(rv)) { + mRestorePresentationEvent = evt.get(); + // The rest of the restore processing will happen on our event + // callback. + *aRestoring = true; + } + + return rv; +} + +namespace { +class MOZ_STACK_CLASS PresentationEventForgetter { + public: + explicit PresentationEventForgetter( + nsRevocableEventPtr& + aRestorePresentationEvent) + : mRestorePresentationEvent(aRestorePresentationEvent), + mEvent(aRestorePresentationEvent.get()) {} + + ~PresentationEventForgetter() { Forget(); } + + void Forget() { + if (mRestorePresentationEvent.get() == mEvent) { + mRestorePresentationEvent.Forget(); + mEvent = nullptr; + } + } + + private: + nsRevocableEventPtr& + mRestorePresentationEvent; + RefPtr mEvent; +}; + +} // namespace + +bool nsDocShell::SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags) { + return (aSandboxFlags & (SANDBOXED_ORIGIN | SANDBOXED_SCRIPTS)) == 0; +} + +nsresult nsDocShell::RestoreFromHistory() { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + MOZ_ASSERT(mRestorePresentationEvent.IsPending()); + PresentationEventForgetter forgetter(mRestorePresentationEvent); + + // This section of code follows the same ordering as CreateContentViewer. + if (!mLSHE) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr viewer = mLSHE->GetContentViewer(); + if (!viewer) { + return NS_ERROR_FAILURE; + } + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + RefPtr doc = viewer->GetDocument(); + nsIRequest* request = nullptr; + if (doc) { + request = doc->GetChannel(); + } + mSavingOldViewer = CanSavePresentation( + mLoadType, request, doc, /* aReportBFCacheComboTelemetry */ false); + } + + // Protect against mLSHE going away via a load triggered from + // pagehide or unload. + nsCOMPtr origLSHE = mLSHE; + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Notify the old content viewer that it's being hidden. + FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + + // If mLSHE was changed as a result of the pagehide event, then + // something else was loaded. Don't finish restoring. + if (mLSHE != origLSHE) { + return NS_OK; + } + + // Add the request to our load group. We do this before swapping out + // the content viewers so that consumers of STATE_START can access + // the old document. We only deal with the toplevel load at this time -- + // to be consistent with normal document loading, subframes cannot start + // loading until after data arrives, which is after STATE_START completes. + + RefPtr currentPresentationRestoration = + mRestorePresentationEvent.get(); + Stop(); + // Make sure we're still restoring the same presentation. + // If we aren't, docshell is in process doing another load already. + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + BeginRestore(viewer, true); + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + forgetter.Forget(); + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + mURIResultedInDocument = true; + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + mPreviousEntryIndex = rootSH->Index(); + rootSH->LegacySHistory()->UpdateIndex(); + mLoadedEntryIndex = rootSH->Index(); + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, + mLoadedEntryIndex)); + } + + // Rather than call Embed(), we will retrieve the viewer from the session + // history entry and swap it in. + // XXX can we refactor this so that we can just call Embed()? + PersistLayoutHistoryState(); + nsresult rv; + if (mContentViewer) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } + + mSavedRefreshURIList = nullptr; + + // In cases where we use a transient about:blank viewer between loads, + // we never show the transient viewer, so _its_ previous viewer is never + // unhooked from the view hierarchy. Destroy any such previous viewer now, + // before we grab the root view sibling, so that we don't grab a view + // that's about to go away. + + if (mContentViewer) { + // Make sure to hold a strong ref to previousViewer here while we + // drop the reference to it from mContentViewer. + nsCOMPtr previousViewer = + mContentViewer->GetPreviousViewer(); + if (previousViewer) { + mContentViewer->SetPreviousViewer(nullptr); + previousViewer->Destroy(); + } + } + + // Save off the root view's parent and sibling so that we can insert the + // new content viewer's root view at the same position. Also save the + // bounds of the root view's widget. + + nsView* rootViewSibling = nullptr; + nsView* rootViewParent = nullptr; + nsIntRect newBounds(0, 0, 0, 0); + + PresShell* oldPresShell = GetPresShell(); + if (oldPresShell) { + nsViewManager* vm = oldPresShell->GetViewManager(); + if (vm) { + nsView* oldRootView = vm->GetRootView(); + + if (oldRootView) { + rootViewSibling = oldRootView->GetNextSibling(); + rootViewParent = oldRootView->GetParent(); + + mContentViewer->GetBounds(newBounds); + } + } + } + + nsCOMPtr container; + RefPtr sibling; + if (rootViewParent && rootViewParent->GetParent()) { + nsIFrame* frame = rootViewParent->GetParent()->GetFrame(); + container = frame ? frame->GetContent() : nullptr; + } + if (rootViewSibling) { + nsIFrame* frame = rootViewSibling->GetFrame(); + sibling = frame ? frame->PresShell()->GetDocument() : nullptr; + } + + // Transfer ownership to mContentViewer. By ensuring that either the + // docshell or the session history, but not both, have references to the + // content viewer, we prevent the viewer from being torn down after + // Destroy() is called. + + if (mContentViewer) { + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + viewer->SetPreviousViewer(mContentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + // Order the mContentViewer setup just like Embed does. + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer.swap(viewer); + + // Grab all of the related presentation from the SHEntry now. + // Clearing the viewer from the SHEntry will clear all of this state. + nsCOMPtr windowState = mLSHE->GetWindowState(); + mLSHE->SetWindowState(nullptr); + + bool sticky = mLSHE->GetSticky(); + + RefPtr document = mContentViewer->GetDocument(); + + nsCOMArray childShells; + int32_t i = 0; + nsCOMPtr child; + while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + childShells.AppendObject(child); + } + + // get the previous content viewer size + nsIntRect oldBounds(0, 0, 0, 0); + mLSHE->GetViewerBounds(oldBounds); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + nsCOMPtr refreshURIList = mLSHE->GetRefreshURIList(); + + // Reattach to the window object. + mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive + rv = mContentViewer->Open(windowState, mLSHE); + mIsRestoringDocument = false; + + // Hack to keep nsDocShellEditorData alive across the + // SetContentViewer(nullptr) call below. + UniquePtr data(mLSHE->ForgetEditorData()); + + // Now remove it from the cached presentation. + mLSHE->SetContentViewer(nullptr); + mEODForCurrentDocument = false; + + mLSHE->SetEditorData(data.release()); + +#ifdef DEBUG + { + nsCOMPtr refreshURIs = mLSHE->GetRefreshURIList(); + nsCOMPtr childShell; + mLSHE->ChildShellAt(0, getter_AddRefs(childShell)); + NS_ASSERTION(!refreshURIs && !childShell, + "SHEntry should have cleared presentation state"); + } +#endif + + // Restore the sticky state of the viewer. The viewer has set this state + // on the history entry in Destroy() just before marking itself non-sticky, + // to avoid teardown of the presentation. + mContentViewer->SetSticky(sticky); + + NS_ENSURE_SUCCESS(rv, rv); + + // mLSHE is now our currently-loaded document. + SetHistoryEntryAndUpdateBC(Nothing(), Some(mLSHE)); + + // We aren't going to restore any items from the LayoutHistoryState, + // but we don't want them to stay around in case the page is reloaded. + SetLayoutHistoryState(nullptr); + + // This is the end of our Embed() replacement + + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + if (document) { + RefPtr parent = GetInProcessParentDocshell(); + if (parent) { + RefPtr d = parent->GetDocument(); + if (d) { + if (d->EventHandlingSuppressed()) { + document->SuppressEventHandling(d->EventHandlingSuppressed()); + } + } + } + + // Use the uri from the mLSHE we had when we entered this function + // (which need not match the document's URI if anchors are involved), + // since that's the history entry we're loading. Note that if we use + // origLSHE we don't have to worry about whether the entry in question + // is still mLSHE or whether it's now mOSHE. + nsCOMPtr uri = origLSHE->GetURI(); + SetCurrentURI(uri, document->GetChannel(), /* aFireLocationChange */ true, + /* aIsInitialAboutBlank */ false, + /* aLocationFlags */ 0); + } + + // This is the end of our CreateContentViewer() replacement. + // Now we simulate a load. First, we restore the state of the javascript + // window object. + nsCOMPtr privWin = GetWindow(); + NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); + + // Now, dispatch a title change event which would happen as the + // is parsed. + document->NotifyPossibleTitleChange(false); + + // Now we simulate appending child docshells for subframes. + for (i = 0; i < childShells.Count(); ++i) { + nsIDocShellTreeItem* childItem = childShells.ObjectAt(i); + nsCOMPtr childShell = do_QueryInterface(childItem); + + // Make sure to not clobber the state of the child. Since AddChild + // always clobbers it, save it off first. + bool allowRedirects; + childShell->GetAllowMetaRedirects(&allowRedirects); + + bool allowSubframes; + childShell->GetAllowSubframes(&allowSubframes); + + bool allowImages; + childShell->GetAllowImages(&allowImages); + + bool allowMedia = childShell->GetAllowMedia(); + + bool allowDNSPrefetch; + childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); + + bool allowContentRetargeting = childShell->GetAllowContentRetargeting(); + bool allowContentRetargetingOnChildren = + childShell->GetAllowContentRetargetingOnChildren(); + + // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that + // the child inherits our state. Among other things, this means that the + // child inherits our mPrivateBrowsingId, which is what we want. + AddChild(childItem); + + childShell->SetAllowMetaRedirects(allowRedirects); + childShell->SetAllowSubframes(allowSubframes); + childShell->SetAllowImages(allowImages); + childShell->SetAllowMedia(allowMedia); + childShell->SetAllowDNSPrefetch(allowDNSPrefetch); + childShell->SetAllowContentRetargeting(allowContentRetargeting); + childShell->SetAllowContentRetargetingOnChildren( + allowContentRetargetingOnChildren); + + rv = childShell->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to restore the window state after adding the child shells back + // to the tree. This is necessary for Thaw() and Resume() to propagate + // properly. + rv = privWin->RestoreWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr presShell = GetPresShell(); + + // We may be displayed on a different monitor (or in a different + // HiDPI mode) than when we got into the history list. So we need + // to check if this has happened. See bug 838239. + + // Because the prescontext normally handles resolution changes via + // a runnable (see nsPresContext::UIResolutionChanged), its device + // context won't be -immediately- updated as a result of calling + // presShell->BackingScaleFactorChanged(). + + // But we depend on that device context when adjusting the view size + // via mContentViewer->SetBounds(newBounds) below. So we need to + // explicitly tell it to check for changed resolution here. + if (presShell) { + RefPtr pc = presShell->GetPresContext(); + if (pc->DeviceContext()->CheckDPIChange()) { + presShell->BackingScaleFactorChanged(); + } + // Recompute zoom and text-zoom and such. + pc->RecomputeBrowsingContextDependentData(); + } + + nsViewManager* newVM = presShell ? presShell->GetViewManager() : nullptr; + nsView* newRootView = newVM ? newVM->GetRootView() : nullptr; + + // Insert the new root view at the correct location in the view tree. + if (container) { + nsSubDocumentFrame* subDocFrame = + do_QueryFrame(container->GetPrimaryFrame()); + rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr; + } else { + rootViewParent = nullptr; + } + if (sibling && sibling->GetPresShell() && + sibling->GetPresShell()->GetViewManager()) { + rootViewSibling = sibling->GetPresShell()->GetViewManager()->GetRootView(); + } else { + rootViewSibling = nullptr; + } + if (rootViewParent && newRootView && + newRootView->GetParent() != rootViewParent) { + nsViewManager* parentVM = rootViewParent->GetViewManager(); + if (parentVM) { + // InsertChild(parent, child, sib, true) inserts the child after + // sib in content order, which is before sib in view order. BUT + // when sib is null it inserts at the end of the the document + // order, i.e., first in view order. But when oldRootSibling is + // null, the old root as at the end of the view list --- last in + // content order --- and we want to call InsertChild(parent, child, + // nullptr, false) in that case. + parentVM->InsertChild(rootViewParent, newRootView, rootViewSibling, + rootViewSibling ? true : false); + + NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, + "error in InsertChild"); + } + } + + nsCOMPtr privWinInner = privWin->GetCurrentInnerWindow(); + + // If parent is suspended, increase suspension count. + // This can't be done as early as event suppression since this + // depends on docshell tree. + privWinInner->SyncStateFromParentWindow(); + + // Now that all of the child docshells have been put into place, we can + // restart the timers for the window and all of the child frames. + privWinInner->Resume(); + + // Now that we have found the inner window of the page restored + // from the history, we have to make sure that + // performance.navigation.type is 2. + Performance* performance = privWinInner->GetPerformance(); + if (performance) { + performance->GetDOMTiming()->NotifyRestoreStart(); + } + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + mRefreshURIList = refreshURIList; + + // Meta-refresh timers have been restarted for this shell, but not + // for our children. Walk the child shells and restart their timers. + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + child->ResumeRefreshURIs(); + } + } + + // Make sure this presentation is the same size as the previous + // presentation. If this is not the same size we showed it at last time, + // then we need to resize the widget. + + // XXXbryner This interacts poorly with Firefox's infobar. If the old + // presentation had the infobar visible, then we will resize the new + // presentation to that smaller size. However, firing the locationchanged + // event will hide the infobar, which will immediately resize the window + // back to the larger size. A future optimization might be to restore + // the presentation at the "wrong" size, then fire the locationchanged + // event and check whether the docshell's new size is the same as the + // cached viewer size (skipping the resize if they are equal). + + if (newRootView) { + if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) { + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("resize widget(%d, %d, %d, %d)", newBounds.x, newBounds.y, + newBounds.width, newBounds.height)); + mContentViewer->SetBounds(newBounds); + } else { + nsIScrollableFrame* rootScrollFrame = + presShell->GetRootScrollFrameAsScrollable(); + if (rootScrollFrame) { + rootScrollFrame->PostScrolledAreaEventForCurrentArea(); + } + } + } + + // The FinishRestore call below can kill these, null them out so we don't + // have invalid pointer lying around. + newRootView = rootViewSibling = rootViewParent = nullptr; + newVM = nullptr; + + // If the IsUnderHiddenEmbedderElement() state has been changed, we need to + // update it. + if (oldPresShell && presShell && + presShell->IsUnderHiddenEmbedderElement() != + oldPresShell->IsUnderHiddenEmbedderElement()) { + presShell->SetIsUnderHiddenEmbedderElement( + oldPresShell->IsUnderHiddenEmbedderElement()); + } + + // Simulate the completion of the load. + nsDocShell::FinishRestore(); + + // Restart plugins, and paint the content. + if (presShell) { + presShell->Thaw(); + } + + return privWin->FireDelayedDOMEvents(true); +} + +nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler) { + if (DocGroup::TryToLoadIframesInBackground()) { + ResetToFirstLoad(); + } + + *aContentHandler = nullptr; + + if (!mTreeOwner || mIsBeingDestroyed) { + // If we don't have a tree owner, then we're in the process of being + // destroyed. Rather than continue trying to load something, just give up. + return NS_ERROR_DOCSHELL_DYING; + } + + if (!mBrowsingContext->AncestorsAreCurrent() || + mBrowsingContext->IsInBFCache()) { + mBrowsingContext->RemoveRootFromBFCacheSync(); + return NS_ERROR_NOT_AVAILABLE; + } + + // Can we check the content type of the current content viewer + // and reuse it without destroying it and re-creating it? + + NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); + + // Instantiate the content viewer object + nsCOMPtr viewer; + nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup, + aContentHandler, getter_AddRefs(viewer)); + + if (NS_FAILED(rv)) { + return rv; + } + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + RefPtr doc = viewer->GetDocument(); + mSavingOldViewer = CanSavePresentation( + mLoadType, aRequest, doc, /* aReportBFCacheComboTelemetry */ false); + } + + NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); + + nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); + if (aOpenedChannel) { + aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); + } + + // Grab the current URI, we need to pass it to Embed, and OnNewURI will reset + // it before we do call Embed. + nsCOMPtr previousURI = mCurrentURI; + + FirePageHideNotification(!mSavingOldViewer); + if (mIsBeingDestroyed) { + // Force to stop the newly created orphaned viewer. + viewer->Stop(); + return NS_ERROR_DOCSHELL_DYING; + } + mLoadingURI = nullptr; + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + // we've created a new document so go ahead and call + // OnNewURI(), but don't fire OnLocationChange() + // notifications before we've called Embed(). See bug 284993. + mURIResultedInDocument = true; + bool errorOnLocationChangeNeeded = false; + nsCOMPtr failedChannel = mFailedChannel; + nsCOMPtr failedURI; + + if (mLoadType == LOAD_ERROR_PAGE) { + // We need to set the SH entry and our current URI here and not + // at the moment we load the page. We want the same behavior + // of Stop() as for a normal page load. See bug 514232 for details. + + // Revert mLoadType to load type to state the page load failed, + // following function calls need it. + mLoadType = mFailedLoadType; + + Document* doc = viewer->GetDocument(); + if (doc) { + doc->SetFailedChannel(failedChannel); + } + + nsCOMPtr triggeringPrincipal; + if (failedChannel) { + // Make sure we have a URI to set currentURI. + NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI)); + } else { + // if there is no failed channel we have to explicitly provide + // a triggeringPrincipal for the history entry. + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + if (!failedURI) { + failedURI = mFailedURI; + } + if (!failedURI) { + // We need a URI object to store a session history entry, so make up a URI + NS_NewURI(getter_AddRefs(failedURI), "about:blank"); + } + + // When we don't have failedURI, something wrong will happen. See + // bug 291876. + MOZ_ASSERT(failedURI, "We don't have a URI for history APIs."); + + mFailedChannel = nullptr; + mFailedURI = nullptr; + + // Create an shistory entry for the old load. + if (failedURI) { + errorOnLocationChangeNeeded = + OnNewURI(failedURI, failedChannel, triggeringPrincipal, nullptr, + nullptr, nullptr, false, false, false); + } + + // Be sure to have a correct mLSHE, it may have been cleared by + // EndPageLoad. See bug 302115. + ChildSHistory* shistory = GetSessionHistory(); + if (!mozilla::SessionHistoryInParent() && shistory && !mLSHE) { + int32_t idx = shistory->LegacySHistory()->GetRequestedIndex(); + if (idx == -1) { + idx = shistory->Index(); + } + shistory->LegacySHistory()->GetEntryAtIndex(idx, getter_AddRefs(mLSHE)); + } + + mLoadType = LOAD_ERROR_PAGE; + } + + nsCOMPtr finalURI; + // If this a redirect, use the final url (uri) + // else use the original url + // + // Note that this should match what documents do (see Document::Reset). + NS_GetFinalChannelURI(aOpenedChannel, getter_AddRefs(finalURI)); + + bool onLocationChangeNeeded = false; + if (finalURI) { + // Pass false for aCloneSHChildren, since we're loading a new page here. + onLocationChangeNeeded = + OnNewURI(finalURI, aOpenedChannel, nullptr, nullptr, nullptr, nullptr, + false, true, false); + } + + // let's try resetting the load group if we need to... + nsCOMPtr currentLoadGroup; + NS_ENSURE_SUCCESS( + aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)), + NS_ERROR_FAILURE); + + if (currentLoadGroup != mLoadGroup) { + nsLoadFlags loadFlags = 0; + + // Cancel any URIs that are currently loading... + // XXX: Need to do this eventually Stop(); + // + // Retarget the document to this loadgroup... + // + /* First attach the channel to the right loadgroup + * and then remove from the old loadgroup. This + * puts the notifications in the right order and + * we don't null-out mLSHE in OnStateChange() for + * all redirected urls + */ + aOpenedChannel->SetLoadGroup(mLoadGroup); + + // Mark the channel as being a document URI... + aOpenedChannel->GetLoadFlags(&loadFlags); + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + nsCOMPtr loadInfo = aOpenedChannel->LoadInfo(); + if (SandboxFlagsImplyCookies(loadInfo->GetSandboxFlags())) { + loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE; + } + + aOpenedChannel->SetLoadFlags(loadFlags); + + mLoadGroup->AddRequest(aRequest, nullptr); + if (currentLoadGroup) { + currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED); + } + + // Update the notification callbacks, so that progress and + // status information are sent to the right docshell... + aOpenedChannel->SetNotificationCallbacks(this); + } + + if (DocGroup::TryToLoadIframesInBackground()) { + if ((!mContentViewer || GetDocument()->IsInitialDocument()) && + IsSubframe()) { + // At this point, we know we just created a new iframe document based on + // the response from the server, and we check if it's a cross-domain + // iframe + + RefPtr newDoc = viewer->GetDocument(); + + RefPtr parent = GetInProcessParentDocshell(); + nsCOMPtr parentPrincipal = + parent->GetDocument()->NodePrincipal(); + nsCOMPtr thisPrincipal = newDoc->NodePrincipal(); + + SiteIdentifier parentSite; + SiteIdentifier thisSite; + + nsresult rv = + BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite); + NS_ENSURE_SUCCESS(rv, rv); + + rv = BasePrincipal::Cast(thisPrincipal)->GetSiteIdentifier(thisSite); + NS_ENSURE_SUCCESS(rv, rv); + + if (!parentSite.Equals(thisSite)) { + if (profiler_thread_is_being_profiled_for_markers()) { + nsCOMPtr prinURI; + BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI)); + nsPrintfCString marker( + "Iframe loaded in background: %s", + nsContentUtils::TruncatedURLForDisplay(prinURI).get()); + PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker); + } + SetBackgroundLoadIframe(); + } + } + } + + NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false, + ShouldAddToSessionHistory(finalURI, aOpenedChannel), + aOpenedChannel, previousURI), + NS_ERROR_FAILURE); + + if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) { + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true)); + } + + if (TreatAsBackgroundLoad()) { + nsCOMPtr triggerParentCheckDocShell = + NewRunnableMethod("nsDocShell::TriggerParentCheckDocShellIsEmpty", this, + &nsDocShell::TriggerParentCheckDocShellIsEmpty); + nsresult rv = NS_DispatchToCurrentThread(triggerParentCheckDocShell); + NS_ENSURE_SUCCESS(rv, rv); + } + + mSavedRefreshURIList = nullptr; + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // if this document is part of a multipart document, + // the ID can be used to distinguish it from the other parts. + nsCOMPtr multiPartChannel(do_QueryInterface(aRequest)); + if (multiPartChannel) { + if (PresShell* presShell = GetPresShell()) { + if (Document* doc = presShell->GetDocument()) { + uint32_t partID; + multiPartChannel->GetPartID(&partID); + doc->SetPartID(partID); + } + } + } + + if (errorOnLocationChangeNeeded) { + FireOnLocationChange(this, failedChannel, failedURI, + LOCATION_CHANGE_ERROR_PAGE); + } else if (onLocationChangeNeeded) { + uint32_t locationFlags = + (mLoadType & LOAD_CMD_RELOAD) ? uint32_t(LOCATION_CHANGE_RELOAD) : 0; + FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags); + } + + return NS_OK; +} + +nsresult nsDocShell::NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, + nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer) { + nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); + + nsCOMPtr docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aContentType); + if (!docLoaderFactory) { + return NS_ERROR_FAILURE; + } + + // Now create an instance of the content viewer nsLayoutDLF makes the + // determination if it should be a "view-source" instead of "view" + nsresult rv = docLoaderFactory->CreateInstance( + "view", aOpenedChannel, aLoadGroup, aContentType, this, nullptr, + aContentHandler, aViewer); + NS_ENSURE_SUCCESS(rv, rv); + + (*aViewer)->SetContainer(this); + return NS_OK; +} + +nsresult nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer, + WindowGlobalChild* aWindowActor) { + MOZ_ASSERT(!mIsBeingDestroyed); + + // + // Copy content viewer state from previous or parent content viewer. + // + // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! + // + // Do NOT to maintain a reference to the old content viewer outside + // of this "copying" block, or it will not be destroyed until the end of + // this routine and all ", + }, + async function (browser) { + let initialState = await SpecialPowers.spawn(browser, [], () => { + return content.history.state; + }); + + is(initialState, null, "history.state should be initially null."); + + let awaitPageShow = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow" + ); + BrowserReload(); + await awaitPageShow; + + let updatedState = await SpecialPowers.spawn(browser, [], () => { + return content.history.state; + }); + is(updatedState, true, "history.state should have been updated."); + } + ); +}); diff --git a/docshell/test/browser/browser_bug1798780.js b/docshell/test/browser/browser_bug1798780.js new file mode 100644 index 0000000000..efc0ed9561 --- /dev/null +++ b/docshell/test/browser/browser_bug1798780.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// The test loads an initial page and then another page which does enough +// fragment navigations so that when going back to the initial page and then +// forward to the last page, the initial page is evicted from the bfcache. +add_task(async function testBFCacheEviction() { + // Make an unrealistic large timeout. + await SpecialPowers.pushPrefEnv({ + set: [["browser.sessionhistory.contentViewerTimeout", 86400]], + }); + + const uri = "data:text/html,initial page"; + const uri2 = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "dummy_page.html"; + + await BrowserTestUtils.withNewTab( + { gBrowser, url: uri }, + async function (browser) { + BrowserTestUtils.loadURIString(browser, uri2); + await BrowserTestUtils.browserLoaded(browser); + + let awaitPageShow = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow" + ); + await SpecialPowers.spawn(browser, [], async function () { + content.location.hash = "1"; + content.location.hash = "2"; + content.location.hash = "3"; + content.history.go(-4); + }); + + await awaitPageShow; + + let awaitPageShow2 = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow" + ); + await SpecialPowers.spawn(browser, [], async function () { + content.history.go(4); + }); + await awaitPageShow2; + ok(true, "Didn't time out."); + } + ); +}); diff --git a/docshell/test/browser/browser_bug234628-1.js b/docshell/test/browser/browser_bug234628-1.js new file mode 100644 index 0000000000..566da65bca --- /dev/null +++ b/docshell/test/browser/browser_bug234628-1.js @@ -0,0 +1,47 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-1.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 129, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 85, + "Child doc should be windows-1252 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 129, + "Parent doc should be windows-1252 subsequently" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 85, + "Child doc should be windows-1252 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "windows-1252", + "Child doc should report windows-1252 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-10.js b/docshell/test/browser/browser_bug234628-10.js new file mode 100644 index 0000000000..8fb51cf27c --- /dev/null +++ b/docshell/test/browser/browser_bug234628-10.js @@ -0,0 +1,46 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-10.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 151, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 71, + "Child doc should be utf-8 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 151, + "Parent doc should be windows-1252 initially" + ); + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 71, + "Child doc should decode as utf-8 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-8", + "Child doc should report UTF-8 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-11.js b/docshell/test/browser/browser_bug234628-11.js new file mode 100644 index 0000000000..d11645ff76 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-11.js @@ -0,0 +1,46 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-11.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 193, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 107, + "Child doc should be utf-8 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 193, + "Parent doc should be windows-1252 subsequently" + ); + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 107, + "Child doc should decode as utf-8 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-8", + "Child doc should report UTF-8 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-2.js b/docshell/test/browser/browser_bug234628-2.js new file mode 100644 index 0000000000..da93dc2ac2 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-2.js @@ -0,0 +1,49 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-2.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 129, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf( + "\u00E2\u201A\u00AC" + ), + 78, + "Child doc should be windows-1252 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 129, + "Parent doc should be windows-1252 subsequently" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 78, + "Child doc should be UTF-8 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-8", + "Child doc should report UTF-8 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-3.js b/docshell/test/browser/browser_bug234628-3.js new file mode 100644 index 0000000000..8a143b51a6 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-3.js @@ -0,0 +1,47 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-3.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 118, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 73, + "Child doc should be utf-8 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 118, + "Parent doc should be windows-1252 subsequently" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 73, + "Child doc should be utf-8 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-8", + "Child doc should report UTF-8 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-4.js b/docshell/test/browser/browser_bug234628-4.js new file mode 100644 index 0000000000..19ec0f8dbf --- /dev/null +++ b/docshell/test/browser/browser_bug234628-4.js @@ -0,0 +1,46 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-4.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 132, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 79, + "Child doc should be utf-8 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 132, + "Parent doc should decode as windows-1252 subsequently" + ); + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 79, + "Child doc should decode as utf-8 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-8", + "Child doc should report UTF-8 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-5.js b/docshell/test/browser/browser_bug234628-5.js new file mode 100644 index 0000000000..77753ed78d --- /dev/null +++ b/docshell/test/browser/browser_bug234628-5.js @@ -0,0 +1,46 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-5.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 146, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 87, + "Child doc should be utf-16 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 146, + "Parent doc should be windows-1252 subsequently" + ); + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 87, + "Child doc should decode as utf-16 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-16LE", + "Child doc should report UTF-16LE subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-6.js b/docshell/test/browser/browser_bug234628-6.js new file mode 100644 index 0000000000..88ff6c1a82 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-6.js @@ -0,0 +1,47 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug234628-6.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 190, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 109, + "Child doc should be utf-16 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 190, + "Parent doc should be windows-1252 subsequently" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 109, + "Child doc should be utf-16 subsequently" + ); + + is( + content.document.characterSet, + "windows-1252", + "Parent doc should report windows-1252 subsequently" + ); + is( + content.frames[0].document.characterSet, + "UTF-16BE", + "Child doc should report UTF-16 subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug234628-8.js b/docshell/test/browser/browser_bug234628-8.js new file mode 100644 index 0000000000..024a3d4d64 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-8.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetCheck(rootDir + "file_bug234628-8.html", afterOpen); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u0402"), + 156, + "Parent doc should be windows-1251" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u0402"), + 99, + "Child doc should be windows-1251" + ); +} diff --git a/docshell/test/browser/browser_bug234628-9.js b/docshell/test/browser/browser_bug234628-9.js new file mode 100644 index 0000000000..ceb7dc4e63 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-9.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetCheck(rootDir + "file_bug234628-9.html", afterOpen); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u20AC"), + 145, + "Parent doc should be UTF-16" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u20AC"), + 96, + "Child doc should be windows-1252" + ); +} diff --git a/docshell/test/browser/browser_bug349769.js b/docshell/test/browser/browser_bug349769.js new file mode 100644 index 0000000000..92e8f78543 --- /dev/null +++ b/docshell/test/browser/browser_bug349769.js @@ -0,0 +1,79 @@ +add_task(async function test() { + const uris = [undefined, "about:blank"]; + + function checkContentProcess(newBrowser, uri) { + return ContentTask.spawn(newBrowser, [uri], async function (uri) { + var prin = content.document.nodePrincipal; + Assert.notEqual( + prin, + null, + "Loaded principal must not be null when adding " + uri + ); + Assert.notEqual( + prin, + undefined, + "Loaded principal must not be undefined when loading " + uri + ); + + Assert.equal( + prin.isSystemPrincipal, + false, + "Loaded principal must not be system when loading " + uri + ); + }); + } + + for (var uri of uris) { + await BrowserTestUtils.withNewTab( + { gBrowser }, + async function (newBrowser) { + let loadedPromise = BrowserTestUtils.browserLoaded(newBrowser); + BrowserTestUtils.loadURIString(newBrowser, uri); + + var prin = newBrowser.contentPrincipal; + isnot( + prin, + null, + "Forced principal must not be null when loading " + uri + ); + isnot( + prin, + undefined, + "Forced principal must not be undefined when loading " + uri + ); + is( + prin.isSystemPrincipal, + false, + "Forced principal must not be system when loading " + uri + ); + + // Belt-and-suspenders e10s check: make sure that the same checks hold + // true in the content process. + await checkContentProcess(newBrowser, uri); + + await loadedPromise; + + prin = newBrowser.contentPrincipal; + isnot( + prin, + null, + "Loaded principal must not be null when adding " + uri + ); + isnot( + prin, + undefined, + "Loaded principal must not be undefined when loading " + uri + ); + is( + prin.isSystemPrincipal, + false, + "Loaded principal must not be system when loading " + uri + ); + + // Belt-and-suspenders e10s check: make sure that the same checks hold + // true in the content process. + await checkContentProcess(newBrowser, uri); + } + ); + } +}); diff --git a/docshell/test/browser/browser_bug388121-1.js b/docshell/test/browser/browser_bug388121-1.js new file mode 100644 index 0000000000..07c476f11b --- /dev/null +++ b/docshell/test/browser/browser_bug388121-1.js @@ -0,0 +1,22 @@ +add_task(async function test() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (newBrowser) { + await SpecialPowers.spawn(newBrowser, [], async function () { + var prin = content.document.nodePrincipal; + Assert.notEqual(prin, null, "Loaded principal must not be null"); + Assert.notEqual( + prin, + undefined, + "Loaded principal must not be undefined" + ); + + Assert.equal( + prin.isSystemPrincipal, + false, + "Loaded principal must not be system" + ); + }); + } + ); +}); diff --git a/docshell/test/browser/browser_bug388121-2.js b/docshell/test/browser/browser_bug388121-2.js new file mode 100644 index 0000000000..695ff1079c --- /dev/null +++ b/docshell/test/browser/browser_bug388121-2.js @@ -0,0 +1,74 @@ +function test() { + waitForExplicitFinish(); + + var w; + var iteration = 1; + const uris = ["", "about:blank"]; + var uri; + var origWgp; + + function testLoad() { + let wgp = w.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal; + if (wgp == origWgp) { + // Go back to polling + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(testLoad, 10); + return; + } + var prin = wgp.documentPrincipal; + isnot(prin, null, "Loaded principal must not be null when adding " + uri); + isnot( + prin, + undefined, + "Loaded principal must not be undefined when loading " + uri + ); + is( + prin.isSystemPrincipal, + false, + "Loaded principal must not be system when loading " + uri + ); + w.close(); + + if (iteration == uris.length) { + finish(); + } else { + ++iteration; + doTest(); + } + } + + function doTest() { + uri = uris[iteration - 1]; + window.open(uri, "_blank", "width=10,height=10,noopener"); + w = Services.wm.getMostRecentWindow("navigator:browser"); + origWgp = w.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal; + var prin = origWgp.documentPrincipal; + if (!uri) { + uri = undefined; + } + isnot(prin, null, "Forced principal must not be null when loading " + uri); + isnot( + prin, + undefined, + "Forced principal must not be undefined when loading " + uri + ); + is( + prin.isSystemPrincipal, + false, + "Forced principal must not be system when loading " + uri + ); + if (uri == undefined) { + // No actual load here, so just move along. + w.close(); + ++iteration; + doTest(); + } else { + // Need to poll, because load listeners on the content window won't + // survive the load. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(testLoad, 10); + } + } + + doTest(); +} diff --git a/docshell/test/browser/browser_bug420605.js b/docshell/test/browser/browser_bug420605.js new file mode 100644 index 0000000000..9a18ab2c9f --- /dev/null +++ b/docshell/test/browser/browser_bug420605.js @@ -0,0 +1,131 @@ +/* Test for Bug 420605 + * https://bugzilla.mozilla.org/show_bug.cgi?id=420605 + */ + +const { PlacesTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PlacesTestUtils.sys.mjs" +); + +add_task(async function test() { + var pageurl = + "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html"; + var fragmenturl = + "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html#firefox"; + + /* Queries nsINavHistoryService and returns a single history entry + * for a given URI */ + function getNavHistoryEntry(aURI) { + var options = PlacesUtils.history.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; + options.maxResults = 1; + + var query = PlacesUtils.history.getNewQuery(); + query.uri = aURI; + var result = PlacesUtils.history.executeQuery(query, options); + result.root.containerOpen = true; + + if (!result.root.childCount) { + return null; + } + return result.root.getChild(0); + } + + // We'll save the favicon URL of the orignal page here and check that the + // page with a hash has the same favicon. + var originalFavicon; + + // Control flow in this test is a bit complicated. + // + // When the page loads, onPageLoad (the DOMContentLoaded handler) and + // favicon-changed are both called, in some order. Once + // they've both run, we click a fragment link in the content page + // (clickLinkIfReady), which should trigger another favicon-changed event, + // this time for the fragment's URL. + + var _clickLinkTimes = 0; + function clickLinkIfReady() { + _clickLinkTimes++; + if (_clickLinkTimes == 2) { + BrowserTestUtils.synthesizeMouseAtCenter( + "#firefox-link", + {}, + gBrowser.selectedBrowser + ); + } + } + + function onPageLoad() { + clickLinkIfReady(); + } + + // Make sure neither of the test pages haven't been loaded before. + var info = getNavHistoryEntry(makeURI(pageurl)); + ok(!info, "The test page must not have been visited already."); + info = getNavHistoryEntry(makeURI(fragmenturl)); + ok(!info, "The fragment test page must not have been visited already."); + + let promiseIcon1 = PlacesTestUtils.waitForNotification( + "favicon-changed", + events => + events.some(e => { + if (e.url == pageurl) { + ok( + e.faviconUrl, + "Favicon value is not null for page without fragment." + ); + originalFavicon = e.faviconUrl; + + // Now that the favicon has loaded, click on fragment link. + // This should trigger the |case fragmenturl| below. + clickLinkIfReady(); + return true; + } + return false; + }) + ); + let promiseIcon2 = PlacesTestUtils.waitForNotification( + "favicon-changed", + events => + events.some(e => { + if (e.url == fragmenturl) { + // If the fragment URL's favicon isn't set, this branch won't + // be called and the test will time out. + is( + e.faviconUrl, + originalFavicon, + "New favicon should be same as original favicon." + ); + ok( + e.faviconUrl, + "Favicon value is not null for page without fragment." + ); + originalFavicon = e.faviconUrl; + + // Now that the favicon has loaded, click on fragment link. + // This should trigger the |case fragmenturl| below. + clickLinkIfReady(); + return true; + } + return false; + }) + ); + + // Now open the test page in a new tab. + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "DOMContentLoaded", + true + ).then(onPageLoad); + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, pageurl); + + await promiseIcon1; + await promiseIcon2; + + // Let's explicitly check that we can get the favicon + // from nsINavHistoryService now. + info = getNavHistoryEntry(makeURI(fragmenturl)); + ok(info, "There must be a history entry for the fragment."); + ok(info.icon, "The history entry must have an associated favicon."); + gBrowser.removeCurrentTab(); +}); diff --git a/docshell/test/browser/browser_bug422543.js b/docshell/test/browser/browser_bug422543.js new file mode 100644 index 0000000000..d3b772619e --- /dev/null +++ b/docshell/test/browser/browser_bug422543.js @@ -0,0 +1,253 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const ACTOR = "Bug422543"; + +let getActor = browser => { + return browser.browsingContext.currentWindowGlobal.getActor(ACTOR); +}; + +add_task(async function runTests() { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + await setupAsync(); + let browser = gBrowser.selectedBrowser; + // Now that we're set up, initialize our frame script. + await checkListenersAsync("initial", "listeners initialized"); + + // Check if all history listeners are always notified. + info("# part 1"); + await whenPageShown(browser, () => + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + BrowserTestUtils.loadURIString(browser, "http://www.example.com/") + ); + await checkListenersAsync("newentry", "shistory has a new entry"); + ok(browser.canGoBack, "we can go back"); + + await whenPageShown(browser, () => browser.goBack()); + await checkListenersAsync("gotoindex", "back to the first shentry"); + ok(browser.canGoForward, "we can go forward"); + + await whenPageShown(browser, () => browser.goForward()); + await checkListenersAsync("gotoindex", "forward to the second shentry"); + + await whenPageShown(browser, () => browser.reload()); + await checkListenersAsync("reload", "current shentry reloaded"); + + await whenPageShown(browser, () => browser.gotoIndex(0)); + await checkListenersAsync("gotoindex", "back to the first index"); + + // Check nsISHistory.notifyOnHistoryReload + info("# part 2"); + ok(await notifyReloadAsync(), "reloading has not been canceled"); + await checkListenersAsync("reload", "saw the reload notification"); + + // Let the first listener cancel the reload action. + info("# part 3"); + await resetListenersAsync(); + await setListenerRetvalAsync(0, false); + ok(!(await notifyReloadAsync()), "reloading has been canceled"); + await checkListenersAsync("reload", "saw the reload notification"); + + // Let both listeners cancel the reload action. + info("# part 4"); + await resetListenersAsync(); + await setListenerRetvalAsync(1, false); + ok(!(await notifyReloadAsync()), "reloading has been canceled"); + await checkListenersAsync("reload", "saw the reload notification"); + + // Let the second listener cancel the reload action. + info("# part 5"); + await resetListenersAsync(); + await setListenerRetvalAsync(0, true); + ok(!(await notifyReloadAsync()), "reloading has been canceled"); + await checkListenersAsync("reload", "saw the reload notification"); + + function sendQuery(message, arg = {}) { + return getActor(gBrowser.selectedBrowser).sendQuery(message, arg); + } + + function checkListenersAsync(aLast, aMessage) { + return sendQuery("getListenerStatus").then(listenerStatuses => { + is(listenerStatuses[0], aLast, aMessage); + is(listenerStatuses[1], aLast, aMessage); + }); + } + + function resetListenersAsync() { + return sendQuery("resetListeners"); + } + + function notifyReloadAsync() { + return sendQuery("notifyReload").then(({ rval }) => { + return rval; + }); + } + + function setListenerRetvalAsync(num, val) { + return sendQuery("setRetval", { num, val }); + } + + async function setupAsync() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888" + ); + + let base = getRootDirectory(gTestPath).slice(0, -1); + ChromeUtils.registerWindowActor(ACTOR, { + child: { + esModuleURI: `${base}/Bug422543Child.sys.mjs`, + }, + }); + + registerCleanupFunction(async () => { + await sendQuery("cleanup"); + gBrowser.removeTab(tab); + + ChromeUtils.unregisterWindowActor(ACTOR); + }); + + await sendQuery("init"); + } + return; + } + + await setup(); + let browser = gBrowser.selectedBrowser; + // Now that we're set up, initialize our frame script. + checkListeners("initial", "listeners initialized"); + + // Check if all history listeners are always notified. + info("# part 1"); + await whenPageShown(browser, () => + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + BrowserTestUtils.loadURIString(browser, "http://www.example.com/") + ); + checkListeners("newentry", "shistory has a new entry"); + ok(browser.canGoBack, "we can go back"); + + await whenPageShown(browser, () => browser.goBack()); + checkListeners("gotoindex", "back to the first shentry"); + ok(browser.canGoForward, "we can go forward"); + + await whenPageShown(browser, () => browser.goForward()); + checkListeners("gotoindex", "forward to the second shentry"); + + await whenPageShown(browser, () => browser.reload()); + checkListeners("reload", "current shentry reloaded"); + + await whenPageShown(browser, () => browser.gotoIndex(0)); + checkListeners("gotoindex", "back to the first index"); + + // Check nsISHistory.notifyOnHistoryReload + info("# part 2"); + ok(notifyReload(browser), "reloading has not been canceled"); + checkListeners("reload", "saw the reload notification"); + + // Let the first listener cancel the reload action. + info("# part 3"); + resetListeners(); + setListenerRetval(0, false); + ok(!notifyReload(browser), "reloading has been canceled"); + checkListeners("reload", "saw the reload notification"); + + // Let both listeners cancel the reload action. + info("# part 4"); + resetListeners(); + setListenerRetval(1, false); + ok(!notifyReload(browser), "reloading has been canceled"); + checkListeners("reload", "saw the reload notification"); + + // Let the second listener cancel the reload action. + info("# part 5"); + resetListeners(); + setListenerRetval(0, true); + ok(!notifyReload(browser), "reloading has been canceled"); + checkListeners("reload", "saw the reload notification"); +}); + +class SHistoryListener { + constructor() { + this.retval = true; + this.last = "initial"; + } + + OnHistoryNewEntry(aNewURI) { + this.last = "newentry"; + } + + OnHistoryGotoIndex() { + this.last = "gotoindex"; + } + + OnHistoryPurge() { + this.last = "purge"; + } + + OnHistoryReload() { + this.last = "reload"; + return this.retval; + } + + OnHistoryReplaceEntry() {} +} +SHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsISHistoryListener", + "nsISupportsWeakReference", +]); + +let listeners = [new SHistoryListener(), new SHistoryListener()]; + +function checkListeners(aLast, aMessage) { + is(listeners[0].last, aLast, aMessage); + is(listeners[1].last, aLast, aMessage); +} + +function resetListeners() { + for (let listener of listeners) { + listener.last = "initial"; + } +} + +function notifyReload(browser) { + return browser.browsingContext.sessionHistory.notifyOnHistoryReload(); +} + +function setListenerRetval(num, val) { + listeners[num].retval = val; +} + +async function setup() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888" + ); + + let browser = tab.linkedBrowser; + registerCleanupFunction(async function () { + for (let listener of listeners) { + browser.browsingContext.sessionHistory.removeSHistoryListener(listener); + } + gBrowser.removeTab(tab); + }); + for (let listener of listeners) { + browser.browsingContext.sessionHistory.addSHistoryListener(listener); + } +} + +function whenPageShown(aBrowser, aNavigation) { + let promise = new Promise(resolve => { + let unregister = BrowserTestUtils.addContentEventListener( + aBrowser, + "pageshow", + () => { + unregister(); + resolve(); + }, + { capture: true } + ); + }); + + aNavigation(); + return promise; +} diff --git a/docshell/test/browser/browser_bug441169.js b/docshell/test/browser/browser_bug441169.js new file mode 100644 index 0000000000..09e83b040c --- /dev/null +++ b/docshell/test/browser/browser_bug441169.js @@ -0,0 +1,44 @@ +/* Make sure that netError won't allow HTML injection through badcert parameters. See bug 441169. */ +var newBrowser; + +function task() { + let resolve; + let promise = new Promise(r => { + resolve = r; + }); + + addEventListener("DOMContentLoaded", checkPage, false); + + function checkPage(event) { + if (event.target != content.document) { + return; + } + removeEventListener("DOMContentLoaded", checkPage, false); + + is( + content.document.getElementById("test_span"), + null, + "Error message should not be parsed as HTML, and hence shouldn't include the 'test_span' element." + ); + resolve(); + } + + var chromeURL = + "about:neterror?e=nssBadCert&u=https%3A//test.kuix.de/&c=UTF-8&d=This%20sentence%20should%20not%20be%20parsed%20to%20include%20a%20%3Cspan%20id=%22test_span%22%3Enamed%3C/span%3E%20span%20tag.%0A%0AThe%20certificate%20is%20only%20valid%20for%20%3Ca%20id=%22cert_domain_link%22%20title=%22kuix.de%22%3Ekuix.de%3C/a%3E%0A%0A(Error%20code%3A%20ssl_error_bad_cert_domain)"; + content.location = chromeURL; + + return promise; +} + +function test() { + waitForExplicitFinish(); + + var newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + newBrowser = gBrowser.getBrowserForTab(newTab); + + ContentTask.spawn(newBrowser, null, task).then(() => { + gBrowser.removeCurrentTab(); + finish(); + }); +} diff --git a/docshell/test/browser/browser_bug503832.js b/docshell/test/browser/browser_bug503832.js new file mode 100644 index 0000000000..8699eb01fc --- /dev/null +++ b/docshell/test/browser/browser_bug503832.js @@ -0,0 +1,76 @@ +/* Test for Bug 503832 + * https://bugzilla.mozilla.org/show_bug.cgi?id=503832 + */ + +add_task(async function () { + var pagetitle = "Page Title for Bug 503832"; + var pageurl = + "http://mochi.test:8888/browser/docshell/test/browser/file_bug503832.html"; + var fragmenturl = + "http://mochi.test:8888/browser/docshell/test/browser/file_bug503832.html#firefox"; + + var historyService = Cc[ + "@mozilla.org/browser/nav-history-service;1" + ].getService(Ci.nsINavHistoryService); + + let fragmentPromise = new Promise(resolve => { + const listener = events => { + const { url, title } = events[0]; + + switch (url) { + case pageurl: + is(title, pagetitle, "Correct page title for " + url); + return; + case fragmenturl: + is(title, pagetitle, "Correct page title for " + url); + // If titles for fragment URLs aren't set, this code + // branch won't be called and the test will timeout, + // resulting in a failure + PlacesObservers.removeListener(["page-title-changed"], listener); + resolve(); + } + }; + + PlacesObservers.addListener(["page-title-changed"], listener); + }); + + /* Queries nsINavHistoryService and returns a single history entry + * for a given URI */ + function getNavHistoryEntry(aURI) { + var options = historyService.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; + options.maxResults = 1; + + var query = historyService.getNewQuery(); + query.uri = aURI; + + var result = historyService.executeQuery(query, options); + result.root.containerOpen = true; + + if (!result.root.childCount) { + return null; + } + var node = result.root.getChild(0); + result.root.containerOpen = false; + return node; + } + + // Make sure neither of the test pages haven't been loaded before. + var info = getNavHistoryEntry(makeURI(pageurl)); + ok(!info, "The test page must not have been visited already."); + info = getNavHistoryEntry(makeURI(fragmenturl)); + ok(!info, "The fragment test page must not have been visited already."); + + // Now open the test page in a new tab + await BrowserTestUtils.openNewForegroundTab(gBrowser, pageurl); + + // Now that the page is loaded, click on fragment link + await BrowserTestUtils.synthesizeMouseAtCenter( + "#firefox-link", + {}, + gBrowser.selectedBrowser + ); + await fragmentPromise; + + gBrowser.removeCurrentTab(); +}); diff --git a/docshell/test/browser/browser_bug554155.js b/docshell/test/browser/browser_bug554155.js new file mode 100644 index 0000000000..223f6241de --- /dev/null +++ b/docshell/test/browser/browser_bug554155.js @@ -0,0 +1,33 @@ +add_task(async function test() { + await BrowserTestUtils.withNewTab( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + { gBrowser, url: "http://example.com" }, + async function (browser) { + let numLocationChanges = 0; + + let listener = { + onLocationChange(browser, webProgress, request, uri, flags) { + info("location change: " + (uri && uri.spec)); + numLocationChanges++; + }, + }; + + gBrowser.addTabsProgressListener(listener); + + await SpecialPowers.spawn(browser, [], function () { + // pushState to a new URL (http://example.com/foo"). This should trigger + // exactly one LocationChange event. + content.history.pushState(null, null, "foo"); + }); + + await Promise.resolve(); + + gBrowser.removeTabsProgressListener(listener); + is( + numLocationChanges, + 1, + "pushState should cause exactly one LocationChange event." + ); + } + ); +}); diff --git a/docshell/test/browser/browser_bug655270.js b/docshell/test/browser/browser_bug655270.js new file mode 100644 index 0000000000..3120abb269 --- /dev/null +++ b/docshell/test/browser/browser_bug655270.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test for Bug 655273 + * + * Call pushState and then make sure that the favicon service associates our + * old favicon with the new URI. + */ + +const { PlacesTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PlacesTestUtils.sys.mjs" +); + +add_task(async function test() { + const testDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + const origURL = testDir + "file_bug655270.html"; + const newURL = origURL + "?new_page"; + + const faviconURL = testDir + "favicon_bug655270.ico"; + + let icon1; + let promiseIcon1 = PlacesTestUtils.waitForNotification( + "favicon-changed", + events => + events.some(e => { + if (e.url == origURL) { + icon1 = e.faviconUrl; + return true; + } + return false; + }) + ); + let icon2; + let promiseIcon2 = PlacesTestUtils.waitForNotification( + "favicon-changed", + events => + events.some(e => { + if (e.url == newURL) { + icon2 = e.faviconUrl; + return true; + } + return false; + }) + ); + + // The page at origURL has a , so we should get a call into + // our observer below when it loads. Once we verify that we have the right + // favicon URI, we call pushState, which should trigger another favicon change + // event, this time for the URI after pushState. + let tab = BrowserTestUtils.addTab(gBrowser, origURL); + await promiseIcon1; + is(icon1, faviconURL, "FaviconURL for original URI"); + // Ignore the promise returned here and wait for the next + // onPageChanged notification. + SpecialPowers.spawn(tab.linkedBrowser, [], function () { + content.history.pushState("", "", "?new_page"); + }); + await promiseIcon2; + is(icon2, faviconURL, "FaviconURL for new URI"); + gBrowser.removeTab(tab); +}); diff --git a/docshell/test/browser/browser_bug655273.js b/docshell/test/browser/browser_bug655273.js new file mode 100644 index 0000000000..e564eb2f6c --- /dev/null +++ b/docshell/test/browser/browser_bug655273.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test for Bug 655273. Make sure that after changing the URI via + * history.pushState, the resulting SHEntry has the same title as our old + * SHEntry. + */ + +add_task(async function test() { + waitForExplicitFinish(); + + await BrowserTestUtils.withNewTab( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + { gBrowser, url: "http://example.com" }, + async function (browser) { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + await SpecialPowers.spawn(browser, [], async function () { + let cw = content; + let oldTitle = cw.document.title; + ok(oldTitle, "Content window should initially have a title."); + cw.history.pushState("", "", "new_page"); + + let shistory = cw.docShell.QueryInterface( + Ci.nsIWebNavigation + ).sessionHistory; + + is( + shistory.legacySHistory.getEntryAtIndex(shistory.index).title, + oldTitle, + "SHEntry title after pushstate." + ); + }); + + return; + } + + let bc = browser.browsingContext; + let oldTitle = browser.browsingContext.currentWindowGlobal.documentTitle; + ok(oldTitle, "Content window should initially have a title."); + SpecialPowers.spawn(browser, [], async function () { + content.history.pushState("", "", "new_page"); + }); + + let shistory = bc.sessionHistory; + await SHListener.waitForHistory(shistory, SHListener.NewEntry); + + is( + shistory.getEntryAtIndex(shistory.index).title, + oldTitle, + "SHEntry title after pushstate." + ); + } + ); +}); diff --git a/docshell/test/browser/browser_bug670318.js b/docshell/test/browser/browser_bug670318.js new file mode 100644 index 0000000000..06c2a10f79 --- /dev/null +++ b/docshell/test/browser/browser_bug670318.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test for Bug 670318 + * + * When LoadEntry() is called on a browser that has multiple duplicate history + * entries, history.index can end up out of range (>= history.count). + */ + +const URL = + "http://mochi.test:8888/browser/docshell/test/browser/file_bug670318.html"; + +add_task(async function test() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + await ContentTask.spawn(browser, URL, async function (URL) { + let history = docShell.QueryInterface( + Ci.nsIWebNavigation + ).sessionHistory; + let count = 0; + + let testDone = {}; + testDone.promise = new Promise(resolve => { + testDone.resolve = resolve; + }); + + // Since listener implements nsISupportsWeakReference, we are + // responsible for keeping it alive so that the GC doesn't clear + // it before the test completes. We do this by anchoring the listener + // to the message manager, and clearing it just before the test + // completes. + this._testListener = { + owner: this, + OnHistoryNewEntry(aNewURI) { + info("OnHistoryNewEntry " + aNewURI.spec + ", " + count); + if (aNewURI.spec == URL && 5 == ++count) { + addEventListener( + "load", + function onLoad() { + Assert.ok( + history.index < history.count, + "history.index is valid" + ); + testDone.resolve(); + }, + { capture: true, once: true } + ); + + history.legacySHistory.removeSHistoryListener( + this.owner._testListener + ); + delete this.owner._testListener; + this.owner = null; + content.setTimeout(() => { + content.location.reload(); + }, 0); + } + }, + + OnHistoryReload: () => true, + OnHistoryGotoIndex: () => {}, + OnHistoryPurge: () => {}, + OnHistoryReplaceEntry: () => { + // The initial load of about:blank causes a transient entry to be + // created, so our first navigation to a real page is a replace + // instead of a new entry. + ++count; + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsISHistoryListener", + "nsISupportsWeakReference", + ]), + }; + + history.legacySHistory.addSHistoryListener(this._testListener); + content.location = URL; + + await testDone.promise; + }); + + return; + } + + let history = browser.browsingContext.sessionHistory; + let count = 0; + + let testDone = {}; + testDone.promise = new Promise(resolve => { + testDone.resolve = resolve; + }); + + let listener = { + async OnHistoryNewEntry(aNewURI) { + if (aNewURI.spec == URL && 5 == ++count) { + history.removeSHistoryListener(listener); + await ContentTask.spawn(browser, null, () => { + return new Promise(resolve => { + addEventListener( + "load", + evt => { + let history = docShell.QueryInterface( + Ci.nsIWebNavigation + ).sessionHistory; + Assert.ok( + history.index < history.count, + "history.index is valid" + ); + resolve(); + }, + { capture: true, once: true } + ); + + content.location.reload(); + }); + }); + testDone.resolve(); + } + }, + + OnHistoryReload: () => true, + OnHistoryGotoIndex: () => {}, + OnHistoryPurge: () => {}, + OnHistoryReplaceEntry: () => { + // The initial load of about:blank causes a transient entry to be + // created, so our first navigation to a real page is a replace + // instead of a new entry. + ++count; + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsISHistoryListener", + "nsISupportsWeakReference", + ]), + }; + + history.addSHistoryListener(listener); + BrowserTestUtils.loadURIString(browser, URL); + + await testDone.promise; + } + ); +}); diff --git a/docshell/test/browser/browser_bug673087-1.js b/docshell/test/browser/browser_bug673087-1.js new file mode 100644 index 0000000000..427b246d76 --- /dev/null +++ b/docshell/test/browser/browser_bug673087-1.js @@ -0,0 +1,46 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug673087-1.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\u00A4"), + 151, + "Parent doc should be windows-1252 initially" + ); + + is( + content.frames[0].document.documentElement.textContent.indexOf("\u00A4"), + 95, + "Child doc should be windows-1252 initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\u3042"), + 151, + "Parent doc should decode as EUC-JP subsequently" + ); + is( + content.frames[0].document.documentElement.textContent.indexOf("\u3042"), + 95, + "Child doc should decode as EUC-JP subsequently" + ); + + is( + content.document.characterSet, + "EUC-JP", + "Parent doc should report EUC-JP subsequently" + ); + is( + content.frames[0].document.characterSet, + "EUC-JP", + "Child doc should report EUC-JP subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug673087-2.js b/docshell/test/browser/browser_bug673087-2.js new file mode 100644 index 0000000000..13a7a2a82c --- /dev/null +++ b/docshell/test/browser/browser_bug673087-2.js @@ -0,0 +1,36 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest( + rootDir + "file_bug673087-2.html", + afterOpen, + afterChangeCharset + ); +} + +function afterOpen() { + is( + content.document.documentElement.textContent.indexOf("\uFFFD"), + 0, + "Doc should decode as replacement initially" + ); + + is( + content.document.characterSet, + "replacement", + "Doc should report replacement initially" + ); +} + +function afterChangeCharset() { + is( + content.document.documentElement.textContent.indexOf("\uFFFD"), + 0, + "Doc should decode as replacement subsequently" + ); + + is( + content.document.characterSet, + "replacement", + "Doc should report replacement subsequently" + ); +} diff --git a/docshell/test/browser/browser_bug673467.js b/docshell/test/browser/browser_bug673467.js new file mode 100644 index 0000000000..507410254c --- /dev/null +++ b/docshell/test/browser/browser_bug673467.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test for bug 673467. In a new tab, load a page which inserts a new iframe +// before the load and then sets its location during the load. This should +// create just one SHEntry. + +var doc = + "data:text/html," + + "" + + ""; + +function test() { + waitForExplicitFinish(); + + let taskFinished; + + let tab = BrowserTestUtils.addTab(gBrowser, doc, {}, tab => { + taskFinished = ContentTask.spawn(tab.linkedBrowser, null, () => { + return new Promise(resolve => { + addEventListener( + "load", + function () { + // The main page has loaded. Now wait for the iframe to load. + let iframe = content.document.getElementById("iframe"); + iframe.addEventListener( + "load", + function listener(aEvent) { + // Wait for the iframe to load the new document, not about:blank. + if (!iframe.src) { + return; + } + + iframe.removeEventListener("load", listener, true); + let shistory = content.docShell.QueryInterface( + Ci.nsIWebNavigation + ).sessionHistory; + + Assert.equal(shistory.count, 1, "shistory count should be 1."); + resolve(); + }, + true + ); + }, + true + ); + }); + }); + }); + + taskFinished.then(() => { + gBrowser.removeTab(tab); + finish(); + }); +} diff --git a/docshell/test/browser/browser_bug852909.js b/docshell/test/browser/browser_bug852909.js new file mode 100644 index 0000000000..108baa0626 --- /dev/null +++ b/docshell/test/browser/browser_bug852909.js @@ -0,0 +1,35 @@ +var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = BrowserTestUtils.addTab( + gBrowser, + rootDir + "file_bug852909.png" + ); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(image); +} + +function image(event) { + ok( + !gBrowser.selectedTab.mayEnableCharacterEncodingMenu, + "Docshell should say the menu should be disabled for images." + ); + + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = BrowserTestUtils.addTab( + gBrowser, + rootDir + "file_bug852909.pdf" + ); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(pdf); +} + +function pdf(event) { + ok( + !gBrowser.selectedTab.mayEnableCharacterEncodingMenu, + "Docshell should say the menu should be disabled for PDF.js." + ); + + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/docshell/test/browser/browser_bug92473.js b/docshell/test/browser/browser_bug92473.js new file mode 100644 index 0000000000..7e386f5ee9 --- /dev/null +++ b/docshell/test/browser/browser_bug92473.js @@ -0,0 +1,70 @@ +/* The test text as octets for reference + * %83%86%83%6a%83%52%81%5b%83%68%82%cd%81%41%82%b7%82%d7%82%c4%82%cc%95%b6%8e%9a%82%c9%8c%c5%97%4c%82%cc%94%d4%8d%86%82%f0%95%74%97%5e%82%b5%82%dc%82%b7 + */ + +function testContent(text) { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [text], text => { + Assert.equal( + content.document.getElementById("testpar").innerHTML, + text, + "

contains expected text" + ); + Assert.equal( + content.document.getElementById("testtextarea").innerHTML, + text, + " + +

+ +

Expected text on load:

+

ѓ†ѓjѓRЃ[ѓh‚НЃA‚·‚Ч‚Д‚М•¶Ћљ‚ЙЊЕ—L‚М”ФЌ†‚р•t—^‚µ‚Ь‚·

+

Expected text on resetting the encoding to Shift_JIS:

+

ユニコードは、すべての文字に固有の番号を付与します

+ + diff --git a/docshell/test/chrome/112564_nocache.html b/docshell/test/chrome/112564_nocache.html new file mode 100644 index 0000000000..29fb990b86 --- /dev/null +++ b/docshell/test/chrome/112564_nocache.html @@ -0,0 +1,10 @@ + + +test1 + + +

+This document will be sent with a no-cache cache-control header. When sent over a secure connection, it should not be stored in bfcache. +

+ + diff --git a/docshell/test/chrome/112564_nocache.html^headers^ b/docshell/test/chrome/112564_nocache.html^headers^ new file mode 100644 index 0000000000..c829a41ae9 --- /dev/null +++ b/docshell/test/chrome/112564_nocache.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-cache diff --git a/docshell/test/chrome/215405_nocache.html b/docshell/test/chrome/215405_nocache.html new file mode 100644 index 0000000000..c7d48c4eba --- /dev/null +++ b/docshell/test/chrome/215405_nocache.html @@ -0,0 +1,14 @@ + + + + test1 + + + + +
Some text
+
Some text
+
Some text
+
Some more text
+ + diff --git a/docshell/test/chrome/215405_nocache.html^headers^ b/docshell/test/chrome/215405_nocache.html^headers^ new file mode 100644 index 0000000000..c829a41ae9 --- /dev/null +++ b/docshell/test/chrome/215405_nocache.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-cache diff --git a/docshell/test/chrome/215405_nostore.html b/docshell/test/chrome/215405_nostore.html new file mode 100644 index 0000000000..4f5bd0f4f0 --- /dev/null +++ b/docshell/test/chrome/215405_nostore.html @@ -0,0 +1,14 @@ + + + + test1 + + + + +
Some text
+
Some text
+
Some text
+
Some more text
+ + diff --git a/docshell/test/chrome/215405_nostore.html^headers^ b/docshell/test/chrome/215405_nostore.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/chrome/215405_nostore.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/chrome/582176_dummy.html b/docshell/test/chrome/582176_dummy.html new file mode 100644 index 0000000000..3b18e512db --- /dev/null +++ b/docshell/test/chrome/582176_dummy.html @@ -0,0 +1 @@ +hello world diff --git a/docshell/test/chrome/582176_xml.xml b/docshell/test/chrome/582176_xml.xml new file mode 100644 index 0000000000..d3dd576dfe --- /dev/null +++ b/docshell/test/chrome/582176_xml.xml @@ -0,0 +1,2 @@ + + diff --git a/docshell/test/chrome/582176_xslt.xsl b/docshell/test/chrome/582176_xslt.xsl new file mode 100644 index 0000000000..5957416899 --- /dev/null +++ b/docshell/test/chrome/582176_xslt.xsl @@ -0,0 +1,8 @@ + + + + XSLT result doc +

xslt result

+ +
+
diff --git a/docshell/test/chrome/662200a.html b/docshell/test/chrome/662200a.html new file mode 100644 index 0000000000..0b9ead6f3e --- /dev/null +++ b/docshell/test/chrome/662200a.html @@ -0,0 +1,8 @@ + + + A + + + Next + + diff --git a/docshell/test/chrome/662200b.html b/docshell/test/chrome/662200b.html new file mode 100644 index 0000000000..91e6b971d6 --- /dev/null +++ b/docshell/test/chrome/662200b.html @@ -0,0 +1,8 @@ + + + B + + + Next + + diff --git a/docshell/test/chrome/662200c.html b/docshell/test/chrome/662200c.html new file mode 100644 index 0000000000..bc00e6b14b --- /dev/null +++ b/docshell/test/chrome/662200c.html @@ -0,0 +1,7 @@ + + + C + + + + diff --git a/docshell/test/chrome/89419.html b/docshell/test/chrome/89419.html new file mode 100644 index 0000000000..b36b8d788c --- /dev/null +++ b/docshell/test/chrome/89419.html @@ -0,0 +1,7 @@ + + +Bug 89419 + + + + diff --git a/docshell/test/chrome/92598_nostore.html b/docshell/test/chrome/92598_nostore.html new file mode 100644 index 0000000000..47bb90441e --- /dev/null +++ b/docshell/test/chrome/92598_nostore.html @@ -0,0 +1,10 @@ + + +test1 + + +

+This document will be sent with a no-store cache-control header. It should not be stored in bfcache. +

+ + diff --git a/docshell/test/chrome/92598_nostore.html^headers^ b/docshell/test/chrome/92598_nostore.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/chrome/92598_nostore.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/chrome/DocShellHelpers.sys.mjs b/docshell/test/chrome/DocShellHelpers.sys.mjs new file mode 100644 index 0000000000..5f4eee8724 --- /dev/null +++ b/docshell/test/chrome/DocShellHelpers.sys.mjs @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +export class DocShellHelpersParent extends JSWindowActorParent { + static eventListener; + + // These static variables should be set when registering the actor + // (currently doPageNavigation in docshell_helpers.js). + static eventsToListenFor; + static observers; + + constructor() { + super(); + } + receiveMessage({ name, data }) { + if (name == "docshell_helpers:event") { + let { event, originalTargetIsHTMLDocument } = data; + + if (this.constructor.eventsToListenFor.includes(event.type)) { + this.constructor.eventListener(event, originalTargetIsHTMLDocument); + } + } else if (name == "docshell_helpers:observe") { + let { topic } = data; + + this.constructor.observers.get(topic).call(); + } + } +} + +export class DocShellHelpersChild extends JSWindowActorChild { + constructor() { + super(); + } + receiveMessage({ name, data }) { + if (name == "docshell_helpers:preventBFCache") { + // Add an RTCPeerConnection to prevent the page from being bfcached. + let win = this.contentWindow; + win.blockBFCache = new win.RTCPeerConnection(); + } + } + handleEvent(event) { + if ( + Document.isInstance(event.originalTarget) && + event.originalTarget.isInitialDocument + ) { + dump(`TEST: ignoring a ${event.type} event for an initial about:blank\n`); + return; + } + + this.sendAsyncMessage("docshell_helpers:event", { + event: { + type: event.type, + persisted: event.persisted, + originalTarget: { + title: event.originalTarget.title, + location: event.originalTarget.location.href, + visibilityState: event.originalTarget.visibilityState, + hidden: event.originalTarget.hidden, + }, + }, + originalTargetIsHTMLDocument: HTMLDocument.isInstance( + event.originalTarget + ), + }); + } + observe(subject, topic) { + if (Window.isInstance(subject) && subject.document.isInitialDocument) { + dump(`TEST: ignoring a topic notification for an initial about:blank\n`); + return; + } + + this.sendAsyncMessage("docshell_helpers:observe", { topic }); + } +} diff --git a/docshell/test/chrome/allowContentRetargeting.sjs b/docshell/test/chrome/allowContentRetargeting.sjs new file mode 100644 index 0000000000..96e467ef68 --- /dev/null +++ b/docshell/test/chrome/allowContentRetargeting.sjs @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function handleRequest(req, resp) { + resp.setHeader("Content-Type", "application/octet-stream", false); + resp.write("hi"); +} diff --git a/docshell/test/chrome/blue.png b/docshell/test/chrome/blue.png new file mode 100644 index 0000000000..8df58f3a5f Binary files /dev/null and b/docshell/test/chrome/blue.png differ diff --git a/docshell/test/chrome/bug112564_window.xhtml b/docshell/test/chrome/bug112564_window.xhtml new file mode 100644 index 0000000000..04c25763b3 --- /dev/null +++ b/docshell/test/chrome/bug112564_window.xhtml @@ -0,0 +1,86 @@ + + + + + + + + + + + + diff --git a/docshell/test/chrome/bug113934_window.xhtml b/docshell/test/chrome/bug113934_window.xhtml new file mode 100644 index 0000000000..794b8b2836 --- /dev/null +++ b/docshell/test/chrome/bug113934_window.xhtml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + diff --git a/docshell/test/chrome/bug215405_window.xhtml b/docshell/test/chrome/bug215405_window.xhtml new file mode 100644 index 0000000000..e6dddd99ba --- /dev/null +++ b/docshell/test/chrome/bug215405_window.xhtml @@ -0,0 +1,177 @@ + + + + + + + + + + + + diff --git a/docshell/test/chrome/bug293235.html b/docshell/test/chrome/bug293235.html new file mode 100644 index 0000000000..458f88431c --- /dev/null +++ b/docshell/test/chrome/bug293235.html @@ -0,0 +1,13 @@ + + + Bug 293235 page1 + + + + This is a test link. + + diff --git a/docshell/test/chrome/bug293235_p2.html b/docshell/test/chrome/bug293235_p2.html new file mode 100644 index 0000000000..2de067b80e --- /dev/null +++ b/docshell/test/chrome/bug293235_p2.html @@ -0,0 +1,8 @@ + + + Bug 293235 page2 + + + Nothing to see here, move along. + + diff --git a/docshell/test/chrome/bug293235_window.xhtml b/docshell/test/chrome/bug293235_window.xhtml new file mode 100644 index 0000000000..7d87517824 --- /dev/null +++ b/docshell/test/chrome/bug293235_window.xhtml @@ -0,0 +1,118 @@ + + + + + + + + + + + diff --git a/docshell/test/chrome/bug294258_testcase.html b/docshell/test/chrome/bug294258_testcase.html new file mode 100644 index 0000000000..cd80fefd06 --- /dev/null +++ b/docshell/test/chrome/bug294258_testcase.html @@ -0,0 +1,43 @@ + + + + + Bug 294258 Testcase + + + + +
+

+ input type="text": +

+

+ input type="checkbox": +

+

+ input type="file": +

+

+ input type="radio": + + +

+

+ textarea: +

+

+ select -> option: +

+
+ + diff --git a/docshell/test/chrome/bug294258_window.xhtml b/docshell/test/chrome/bug294258_window.xhtml new file mode 100644 index 0000000000..fe47f3e70f --- /dev/null +++ b/docshell/test/chrome/bug294258_window.xhtml @@ -0,0 +1,72 @@ + + + + + + + + + diff --git a/docshell/test/chrome/bug298622_window.xhtml b/docshell/test/chrome/bug298622_window.xhtml new file mode 100644 index 0000000000..38abf35107 --- /dev/null +++ b/docshell/test/chrome/bug298622_window.xhtml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + diff --git a/docshell/test/chrome/bug301397_1.html b/docshell/test/chrome/bug301397_1.html new file mode 100644 index 0000000000..9943c2efe6 --- /dev/null +++ b/docshell/test/chrome/bug301397_1.html @@ -0,0 +1,9 @@ + + + + iframe parent + + + " + + "'>" + + "" + + ""; + + await promisePageNavigation({ + uri: test3Doc, + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test2", persisted: true}, + {type: "load", title: "test3-nested2"}, + {type: "pageshow", title: "test3-nested2", persisted: false}, + {type: "load", title: "test3-nested1"}, + {type: "pageshow", title: "test3-nested1", persisted: false}, + {type: "load", title: "test3"}, + {type: "pageshow", title: "test3", persisted: false}], + }); + + var test4Doc = "data:text/html,test4" + + "test4"; + + await promisePageNavigation({ + uri: test4Doc, + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test3", persisted: true}, + {type: "pagehide", title: "test3-nested1", persisted: true}, + {type: "pagehide", title: "test3-nested2", persisted: true}, + {type: "load", title: "test4"}, + {type: "pageshow", title: "test4", persisted: false}], + }); + + await promisePageNavigation({ + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test4", persisted: true}, + {type: "pageshow", title: "test3-nested2", persisted: true}, + {type: "pageshow", title: "test3-nested1", persisted: true}, + {type: "pageshow", title: "test3", persisted: true}], + }); + + // This is where the two nested pagehide are not dispatched in bug 364461 + await promisePageNavigation({ + forward: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test3", persisted: true}, + {type: "pagehide", title: "test3-nested1", persisted: true}, + {type: "pagehide", title: "test3-nested2", persisted: true}, + {type: "pageshow", title: "test4", persisted: true}], + }); + + // Tests 5 + 6: + // Back/forward between a document containing an unload handler and a + // a simple document. Bfcache won't be used for the first one (see + // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching). + + var test5Doc = "data:text/html,test5" + + "" + + "test5"; + + await promisePageNavigation({ + uri: test5Doc, + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test4", persisted: true}, + {type: "load", title: "test5"}, + {type: "pageshow", title: "test5", persisted: false}], + }); + + var test6Doc = "data:text/html,test6" + + "test6"; + + await promisePageNavigation({ + uri: test6Doc, + eventsToListenFor: ["load", "unload", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test5", persisted: false}, + {type: "unload", title: "test5"}, + {type: "load", title: "test6"}, + {type: "pageshow", title: "test6", persisted: false}], + }); + + await promisePageNavigation({ + back: true, + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test6", persisted: true}, + {type: "load", title: "test5"}, + {type: "pageshow", title: "test5", persisted: false}], + }); + + await promisePageNavigation({ + forward: true, + eventsToListenFor: ["unload", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test5", persisted: false}, + {type: "unload", title: "test5"}, + {type: "pageshow", title: "test6", persisted: true}], + }); + + // Test 7: + // Testcase from https://bugzilla.mozilla.org/show_bug.cgi?id=384977#c10 + // Check that navigation is not blocked after a document is restored + // from bfcache + + var test7Doc = "data:text/html,test7" + + "" + + "" + + ""; + + await promisePageNavigation({ + uri: test7Doc, + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test6", persisted: true}, + {type: "load", title: "test7-nested1"}, + {type: "pageshow", title: "test7-nested1", persisted: false}, + {type: "load", title: "test7"}, + {type: "pageshow", title: "test7", persisted: false}], + }); + + // Simulates a click on the link inside the iframe + function clickIframeLink() { + SpecialPowers.spawn(TestWindow.getBrowser(), [], () => { + var iframe = content.document.getElementsByTagName("iframe")[0]; + var w = iframe.contentWindow; + var d = iframe.contentDocument; + + var evt = d.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, w, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + d.getElementsByTagName("a")[0].dispatchEvent(evt); + }); + } + + let clicked = promisePageNavigation({ + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test7", persisted: true}, + {type: "pagehide", title: "test7-nested1", persisted: true}, + {type: "load"}, + {type: "pageshow", persisted: false}], + waitForEventsOnly: true, + }); + clickIframeLink(); + await clicked; + + is(gBrowser.currentURI.spec, "data:text/plain,aaa", + "Navigation is blocked when clicking link"); + + await promisePageNavigation({ + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", persisted: true}, + {type: "pageshow", title: "test7-nested1", persisted: true}, + {type: "pageshow", title: "test7", persisted: true}], + }); + + clicked = promisePageNavigation({ + eventsToListenFor: ["load", "pageshow", "pagehide"], + expectedEvents: [{type: "pagehide", title: "test7", persisted: true}, + {type: "pagehide", title: "test7-nested1", persisted: true}, + {type: "load"}, + {type: "pageshow", persisted: false}], + waitForEventsOnly: true, + }); + clickIframeLink(); + await clicked; + + is(gBrowser.currentURI.spec, "data:text/plain,aaa", + "Navigation is blocked when clicking link"); + + Services.prefs.clearUserPref("browser.navigation.requireUserInteraction"); + finish(); + } + ]]> + + + diff --git a/docshell/test/chrome/bug396519_window.xhtml b/docshell/test/chrome/bug396519_window.xhtml new file mode 100644 index 0000000000..a5ecbeb3e8 --- /dev/null +++ b/docshell/test/chrome/bug396519_window.xhtml @@ -0,0 +1,132 @@ + + + + + + + + + + + + diff --git a/docshell/test/chrome/bug396649_window.xhtml b/docshell/test/chrome/bug396649_window.xhtml new file mode 100644 index 0000000000..c378f11dc3 --- /dev/null +++ b/docshell/test/chrome/bug396649_window.xhtml @@ -0,0 +1,119 @@ + + + + + + + + + diff --git a/docshell/test/chrome/bug449778_window.xhtml b/docshell/test/chrome/bug449778_window.xhtml new file mode 100644 index 0000000000..3197b7acf4 --- /dev/null +++ b/docshell/test/chrome/bug449778_window.xhtml @@ -0,0 +1,107 @@ + + + + + + + + + third test"; + + + $("f1").setAttribute("src", doc1); + $("f2").setAttribute("src", doc2); + + function doTheTest() { + var strs = { "f1": "", "f2" : "" }; + function attachListener(node, type) { + var listener = function(e) { + if (strs[node.id]) strs[node.id] += " "; + strs[node.id] += node.id + ".page" + type; + } + node.addEventListener("page" + type, listener); + + listener.detach = function() { + node.removeEventListener("page" + type, listener); + } + return listener; + } + + var l1 = attachListener($("f1"), "show"); + var l2 = attachListener($("f1"), "hide"); + var l3 = attachListener($("f2"), "show"); + var l4 = attachListener($("f2"), "hide"); + + $("f1").swapDocShells($("f2")); + + is(strs.f1, "f1.pagehide f1.pageshow", + "Expected hide then show on first loaded page"); + is(strs.f2, "f2.pagehide f2.pageshow", + "Expected hide then show on second loaded page"); + + function listener2() { + $("f2").removeEventListener("testEvt", listener2); + + strs = { "f1": "", "f2" : "" }; + + $("f1").swapDocShells($("f2")); + is(strs.f1, "f1.pagehide", + "Expected hide on already-loaded page, then nothing"); + is(strs.f2, "f2.pageshow f2.pagehide f2.pageshow", + "Expected show on still-loading page, then hide on it, then show " + + "on already-loaded page"); + + strs = { "f1": "", "f2" : "" }; + + $("f1").addEventListener("pageshow", listener3); + } + + function listener3() { + $("f1").removeEventListener("pageshow", listener3); + + is(strs.f1, "f1.pageshow", + "Expected show as our page finishes loading"); + is(strs.f2, "", "Expected no more events here."); + + l1.detach(); + l2.detach(); + l3.detach(); + l4.detach(); + + window.close(); + SimpleTest.finish(); + } + + $("f2").addEventListener("testEvt", listener2, false, true); + $("f2").setAttribute("src", doc3); + } + + ]]> + diff --git a/docshell/test/chrome/bug449780_window.xhtml b/docshell/test/chrome/bug449780_window.xhtml new file mode 100644 index 0000000000..c37bc096b2 --- /dev/null +++ b/docshell/test/chrome/bug449780_window.xhtml @@ -0,0 +1,83 @@ + + + + + + + + + + diff --git a/docshell/test/chrome/bug454235-subframe.xhtml b/docshell/test/chrome/bug454235-subframe.xhtml new file mode 100644 index 0000000000..a8b6178e65 --- /dev/null +++ b/docshell/test/chrome/bug454235-subframe.xhtml @@ -0,0 +1,7 @@ + + + + + + diff --git a/docshell/test/chrome/bug582176_window.xhtml b/docshell/test/chrome/bug582176_window.xhtml new file mode 100644 index 0000000000..b43220a2fe --- /dev/null +++ b/docshell/test/chrome/bug582176_window.xhtml @@ -0,0 +1,74 @@ + + + + + + + + diff --git a/docshell/test/chrome/bug608669.xhtml b/docshell/test/chrome/bug608669.xhtml new file mode 100644 index 0000000000..993f24051c --- /dev/null +++ b/docshell/test/chrome/bug608669.xhtml @@ -0,0 +1,14 @@ + + + + + + diff --git a/docshell/test/chrome/bug662200_window.xhtml b/docshell/test/chrome/bug662200_window.xhtml new file mode 100644 index 0000000000..7c6f656f26 --- /dev/null +++ b/docshell/test/chrome/bug662200_window.xhtml @@ -0,0 +1,119 @@ + + + + + + + + + diff --git a/docshell/test/chrome/bug690056_window.xhtml b/docshell/test/chrome/bug690056_window.xhtml new file mode 100644 index 0000000000..fb5dfaea40 --- /dev/null +++ b/docshell/test/chrome/bug690056_window.xhtml @@ -0,0 +1,171 @@ + + + + + + + + + diff --git a/docshell/test/chrome/bug846906.html b/docshell/test/chrome/bug846906.html new file mode 100644 index 0000000000..a289417ea8 --- /dev/null +++ b/docshell/test/chrome/bug846906.html @@ -0,0 +1,10 @@ + + + + + + +
+
+ + diff --git a/docshell/test/chrome/bug89419.sjs b/docshell/test/chrome/bug89419.sjs new file mode 100644 index 0000000000..7172690a9a --- /dev/null +++ b/docshell/test/chrome/bug89419.sjs @@ -0,0 +1,12 @@ +function handleRequest(request, response) { + var redirectstate = "/docshell/test/chrome/bug89419.sjs"; + response.setStatusLine("1.1", 302, "Found"); + if (getState(redirectstate) == "") { + response.setHeader("Location", "red.png", false); + setState(redirectstate, "red"); + } else { + response.setHeader("Location", "blue.png", false); + setState(redirectstate, ""); + } + response.setHeader("Cache-Control", "no-cache", false); +} diff --git a/docshell/test/chrome/bug89419_window.xhtml b/docshell/test/chrome/bug89419_window.xhtml new file mode 100644 index 0000000000..12b9dec650 --- /dev/null +++ b/docshell/test/chrome/bug89419_window.xhtml @@ -0,0 +1,69 @@ + + + + + + + + + + + diff --git a/docshell/test/chrome/bug909218.html b/docshell/test/chrome/bug909218.html new file mode 100644 index 0000000000..a11fa6000d --- /dev/null +++ b/docshell/test/chrome/bug909218.html @@ -0,0 +1,11 @@ + + + + + + + + + + + + diff --git a/docshell/test/chrome/gen_template.pl b/docshell/test/chrome/gen_template.pl new file mode 100644 index 0000000000..109d6161cd --- /dev/null +++ b/docshell/test/chrome/gen_template.pl @@ -0,0 +1,39 @@ +#!/usr/bin/perl + +# This script makes docshell test case templates. It takes one argument: +# +# -b: a bugnumber +# +# For example, this command: +# +# perl gen_template.pl -b 303267 +# +# Writes test case template files test_bug303267.xhtml and bug303267_window.xhtml +# to the current directory. + +use FindBin; +use Getopt::Long; +GetOptions("b=i"=> \$bug_number); + +$template = "$FindBin::RealBin/test.template.txt"; + +open(IN,$template) or die("Failed to open input file for reading."); +open(OUT, ">>test_bug" . $bug_number . ".xhtml") or die("Failed to open output file for appending."); +while((defined(IN)) && ($line = )) { + $line =~ s/{BUGNUMBER}/$bug_number/g; + print OUT $line; +} +close(IN); +close(OUT); + +$template = "$FindBin::RealBin/window.template.txt"; + +open(IN,$template) or die("Failed to open input file for reading."); +open(OUT, ">>bug" . $bug_number . "_window.xhtml") or die("Failed to open output file for appending."); +while((defined(IN)) && ($line = )) { + $line =~ s/{BUGNUMBER}/$bug_number/g; + print OUT $line; +} +close(IN); +close(OUT); + diff --git a/docshell/test/chrome/generic.html b/docshell/test/chrome/generic.html new file mode 100644 index 0000000000..569a78c05a --- /dev/null +++ b/docshell/test/chrome/generic.html @@ -0,0 +1,12 @@ + + + + generic page + + + +
+ A generic page which can be used any time a test needs to load an arbitrary page via http. +
+ + diff --git a/docshell/test/chrome/mozFrameType_window.xhtml b/docshell/test/chrome/mozFrameType_window.xhtml new file mode 100644 index 0000000000..e5d0126d22 --- /dev/null +++ b/docshell/test/chrome/mozFrameType_window.xhtml @@ -0,0 +1,49 @@ + + + + + + + + + diff --git a/docshell/test/chrome/red.png b/docshell/test/chrome/red.png new file mode 100644 index 0000000000..aa9ce25263 Binary files /dev/null and b/docshell/test/chrome/red.png differ diff --git a/docshell/test/chrome/test.template.txt b/docshell/test/chrome/test.template.txt new file mode 100644 index 0000000000..b7dd5e5c23 --- /dev/null +++ b/docshell/test/chrome/test.template.txt @@ -0,0 +1,41 @@ + + + + + + + Test for Bug {BUGNUMBER} + + + + + Mozilla Bug {BUGNUMBER} +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_allowContentRetargeting.html b/docshell/test/chrome/test_allowContentRetargeting.html new file mode 100644 index 0000000000..b6b830138f --- /dev/null +++ b/docshell/test/chrome/test_allowContentRetargeting.html @@ -0,0 +1,76 @@ + + + + + + + + + +

+

+ + diff --git a/docshell/test/chrome/test_bug112564.xhtml b/docshell/test/chrome/test_bug112564.xhtml new file mode 100644 index 0000000000..83a087b6d9 --- /dev/null +++ b/docshell/test/chrome/test_bug112564.xhtml @@ -0,0 +1,37 @@ + + + + + + + + + +Mozilla Bug 112564 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug113934.xhtml b/docshell/test/chrome/test_bug113934.xhtml new file mode 100644 index 0000000000..99b8ae253c --- /dev/null +++ b/docshell/test/chrome/test_bug113934.xhtml @@ -0,0 +1,29 @@ + + + + + + + + + + + Mozilla Bug 396519 + + + + + diff --git a/docshell/test/chrome/test_bug215405.xhtml b/docshell/test/chrome/test_bug215405.xhtml new file mode 100644 index 0000000000..e9511a6ed1 --- /dev/null +++ b/docshell/test/chrome/test_bug215405.xhtml @@ -0,0 +1,37 @@ + + + + + + + + + +Mozilla Bug 215405 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug293235.xhtml b/docshell/test/chrome/test_bug293235.xhtml new file mode 100644 index 0000000000..4696c37557 --- /dev/null +++ b/docshell/test/chrome/test_bug293235.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 293235 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug294258.xhtml b/docshell/test/chrome/test_bug294258.xhtml new file mode 100644 index 0000000000..8f61a6c299 --- /dev/null +++ b/docshell/test/chrome/test_bug294258.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 294258 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug298622.xhtml b/docshell/test/chrome/test_bug298622.xhtml new file mode 100644 index 0000000000..8a154b5145 --- /dev/null +++ b/docshell/test/chrome/test_bug298622.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 298622 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug301397.xhtml b/docshell/test/chrome/test_bug301397.xhtml new file mode 100644 index 0000000000..3da88ecf53 --- /dev/null +++ b/docshell/test/chrome/test_bug301397.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 301397 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug303267.xhtml b/docshell/test/chrome/test_bug303267.xhtml new file mode 100644 index 0000000000..03af56d344 --- /dev/null +++ b/docshell/test/chrome/test_bug303267.xhtml @@ -0,0 +1,39 @@ + + + + + + + + + +Mozilla Bug 303267 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug311007.xhtml b/docshell/test/chrome/test_bug311007.xhtml new file mode 100644 index 0000000000..4601c6ec20 --- /dev/null +++ b/docshell/test/chrome/test_bug311007.xhtml @@ -0,0 +1,42 @@ + + + + + + + + + + + Mozilla Bug 311007 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug321671.xhtml b/docshell/test/chrome/test_bug321671.xhtml new file mode 100644 index 0000000000..7855db6f13 --- /dev/null +++ b/docshell/test/chrome/test_bug321671.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 321671 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug360511.xhtml b/docshell/test/chrome/test_bug360511.xhtml new file mode 100644 index 0000000000..99b9d180f4 --- /dev/null +++ b/docshell/test/chrome/test_bug360511.xhtml @@ -0,0 +1,39 @@ + + + + + + + + + + + Mozilla Bug 360511 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug364461.xhtml b/docshell/test/chrome/test_bug364461.xhtml new file mode 100644 index 0000000000..9bdc016ae7 --- /dev/null +++ b/docshell/test/chrome/test_bug364461.xhtml @@ -0,0 +1,43 @@ + + + + + + + + + +Mozilla Bug 364461 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug396519.xhtml b/docshell/test/chrome/test_bug396519.xhtml new file mode 100644 index 0000000000..7c99bf4a32 --- /dev/null +++ b/docshell/test/chrome/test_bug396519.xhtml @@ -0,0 +1,28 @@ + + + + + + + + + + Mozilla Bug 396519 + + + + + diff --git a/docshell/test/chrome/test_bug396649.xhtml b/docshell/test/chrome/test_bug396649.xhtml new file mode 100644 index 0000000000..8ac05eee85 --- /dev/null +++ b/docshell/test/chrome/test_bug396649.xhtml @@ -0,0 +1,41 @@ + + + + + + + + + + + Mozilla Bug 396649 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug428288.html b/docshell/test/chrome/test_bug428288.html new file mode 100644 index 0000000000..4697aed515 --- /dev/null +++ b/docshell/test/chrome/test_bug428288.html @@ -0,0 +1,37 @@ + + + + + Test for Bug 428288 + + + + +Mozilla Bug 428288 +

+ +
+
+
+ + + diff --git a/docshell/test/chrome/test_bug449778.xhtml b/docshell/test/chrome/test_bug449778.xhtml new file mode 100644 index 0000000000..67e17164ea --- /dev/null +++ b/docshell/test/chrome/test_bug449778.xhtml @@ -0,0 +1,29 @@ + + + + + + + + + + + Mozilla Bug 396519 + + + + + diff --git a/docshell/test/chrome/test_bug449780.xhtml b/docshell/test/chrome/test_bug449780.xhtml new file mode 100644 index 0000000000..43ed3ce25d --- /dev/null +++ b/docshell/test/chrome/test_bug449780.xhtml @@ -0,0 +1,29 @@ + + + + + + + + + + + Mozilla Bug 396519 + + + + + diff --git a/docshell/test/chrome/test_bug453650.xhtml b/docshell/test/chrome/test_bug453650.xhtml new file mode 100644 index 0000000000..2283e29206 --- /dev/null +++ b/docshell/test/chrome/test_bug453650.xhtml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + Mozilla Bug 453650 + + diff --git a/docshell/test/chrome/test_bug454235.xhtml b/docshell/test/chrome/test_bug454235.xhtml new file mode 100644 index 0000000000..d64f701f8e --- /dev/null +++ b/docshell/test/chrome/test_bug454235.xhtml @@ -0,0 +1,40 @@ + + + + + + + + + + Mozilla Bug 454235 + + + + + + + + diff --git a/docshell/test/chrome/test_bug456980.xhtml b/docshell/test/chrome/test_bug456980.xhtml new file mode 100644 index 0000000000..d1741209c6 --- /dev/null +++ b/docshell/test/chrome/test_bug456980.xhtml @@ -0,0 +1,29 @@ + + + + + + + + + + + Mozilla Bug 396519 + + + + + diff --git a/docshell/test/chrome/test_bug565388.xhtml b/docshell/test/chrome/test_bug565388.xhtml new file mode 100644 index 0000000000..23af2c146d --- /dev/null +++ b/docshell/test/chrome/test_bug565388.xhtml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + Mozilla Bug 565388 + + diff --git a/docshell/test/chrome/test_bug582176.xhtml b/docshell/test/chrome/test_bug582176.xhtml new file mode 100644 index 0000000000..5f189ae87b --- /dev/null +++ b/docshell/test/chrome/test_bug582176.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 582176 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug608669.xhtml b/docshell/test/chrome/test_bug608669.xhtml new file mode 100644 index 0000000000..f6a62b0802 --- /dev/null +++ b/docshell/test/chrome/test_bug608669.xhtml @@ -0,0 +1,80 @@ + + + + + + + + + + Mozilla Bug 608669 + + + + + diff --git a/docshell/test/chrome/test_bug662200.xhtml b/docshell/test/chrome/test_bug662200.xhtml new file mode 100644 index 0000000000..19a386ba97 --- /dev/null +++ b/docshell/test/chrome/test_bug662200.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 662200 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug690056.xhtml b/docshell/test/chrome/test_bug690056.xhtml new file mode 100644 index 0000000000..5f7a2b9534 --- /dev/null +++ b/docshell/test/chrome/test_bug690056.xhtml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/docshell/test/chrome/test_bug789773.xhtml b/docshell/test/chrome/test_bug789773.xhtml new file mode 100644 index 0000000000..0f4a67fc10 --- /dev/null +++ b/docshell/test/chrome/test_bug789773.xhtml @@ -0,0 +1,67 @@ + + + + + + + diff --git a/docshell/test/chrome/test_bug846906.xhtml b/docshell/test/chrome/test_bug846906.xhtml new file mode 100644 index 0000000000..74bfdf7036 --- /dev/null +++ b/docshell/test/chrome/test_bug846906.xhtml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + Mozilla Bug 846906 + + diff --git a/docshell/test/chrome/test_bug89419.xhtml b/docshell/test/chrome/test_bug89419.xhtml new file mode 100644 index 0000000000..848982180f --- /dev/null +++ b/docshell/test/chrome/test_bug89419.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + Mozilla Bug 89419 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_bug909218.html b/docshell/test/chrome/test_bug909218.html new file mode 100644 index 0000000000..bcbcc176eb --- /dev/null +++ b/docshell/test/chrome/test_bug909218.html @@ -0,0 +1,117 @@ + + + + + + + + + diff --git a/docshell/test/chrome/test_bug92598.xhtml b/docshell/test/chrome/test_bug92598.xhtml new file mode 100644 index 0000000000..1b89e3eeb3 --- /dev/null +++ b/docshell/test/chrome/test_bug92598.xhtml @@ -0,0 +1,37 @@ + + + + + + + + + +Mozilla Bug 92598 +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_docRedirect.sjs b/docshell/test/chrome/test_docRedirect.sjs new file mode 100644 index 0000000000..4050eb06d7 --- /dev/null +++ b/docshell/test/chrome/test_docRedirect.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 301, "Moved Permanently"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + response.setHeader("Location", "http://example.org/"); + response.write("Hello world!"); +} diff --git a/docshell/test/chrome/test_docRedirect.xhtml b/docshell/test/chrome/test_docRedirect.xhtml new file mode 100644 index 0000000000..1688d9823e --- /dev/null +++ b/docshell/test/chrome/test_docRedirect.xhtml @@ -0,0 +1,91 @@ + + + + + + + + + + Mozilla Bug 1342989 + + diff --git a/docshell/test/chrome/test_mozFrameType.xhtml b/docshell/test/chrome/test_mozFrameType.xhtml new file mode 100644 index 0000000000..f9740a761b --- /dev/null +++ b/docshell/test/chrome/test_mozFrameType.xhtml @@ -0,0 +1,42 @@ + + + + + + + + + + +

+ +
+
+ + + + +
diff --git a/docshell/test/chrome/test_open_and_immediately_close_opener.html b/docshell/test/chrome/test_open_and_immediately_close_opener.html new file mode 100644 index 0000000000..bb5f6f054d --- /dev/null +++ b/docshell/test/chrome/test_open_and_immediately_close_opener.html @@ -0,0 +1,54 @@ + + + + Test for Bug 1702678 + + + + + +Mozilla Bug 1702678 + + + + + + diff --git a/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml new file mode 100644 index 0000000000..0cc45c7821 --- /dev/null +++ b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml @@ -0,0 +1,159 @@ + + + + + + + diff --git a/docshell/test/chrome/window.template.txt b/docshell/test/chrome/window.template.txt new file mode 100644 index 0000000000..4c520dc075 --- /dev/null +++ b/docshell/test/chrome/window.template.txt @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/docshell/test/iframesandbox/file_child_navigation_by_location.html b/docshell/test/iframesandbox/file_child_navigation_by_location.html new file mode 100644 index 0000000000..7365bed81f --- /dev/null +++ b/docshell/test/iframesandbox/file_child_navigation_by_location.html @@ -0,0 +1 @@ + diff --git a/docshell/test/iframesandbox/file_marquee_event_handlers.html b/docshell/test/iframesandbox/file_marquee_event_handlers.html new file mode 100644 index 0000000000..13ee31ddb7 --- /dev/null +++ b/docshell/test/iframesandbox/file_marquee_event_handlers.html @@ -0,0 +1,17 @@ + + + + +Test marquee attribute event handlers in iframe sandbox + + + + + Will bounce and finish + + + diff --git a/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html new file mode 100644 index 0000000000..ad24c0f242 --- /dev/null +++ b/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html @@ -0,0 +1,15 @@ + + + + +Test window for other auxiliary navigation by location tests + + + diff --git a/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html new file mode 100644 index 0000000000..978980df25 --- /dev/null +++ b/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html @@ -0,0 +1,15 @@ + + + + +Test window for our auxiliary navigation by location tests + + + diff --git a/docshell/test/iframesandbox/file_parent_navigation_by_location.html b/docshell/test/iframesandbox/file_parent_navigation_by_location.html new file mode 100644 index 0000000000..9a2e95fad0 --- /dev/null +++ b/docshell/test/iframesandbox/file_parent_navigation_by_location.html @@ -0,0 +1,18 @@ + + + + +Test window for parent navigation by location tests + + + + + + diff --git a/docshell/test/iframesandbox/file_sibling_navigation_by_location.html b/docshell/test/iframesandbox/file_sibling_navigation_by_location.html new file mode 100644 index 0000000000..51a52bb8eb --- /dev/null +++ b/docshell/test/iframesandbox/file_sibling_navigation_by_location.html @@ -0,0 +1,15 @@ + + + + +Test window for sibling navigation by location tests + + + diff --git a/docshell/test/iframesandbox/file_top_navigation_by_location.html b/docshell/test/iframesandbox/file_top_navigation_by_location.html new file mode 100644 index 0000000000..194430f38c --- /dev/null +++ b/docshell/test/iframesandbox/file_top_navigation_by_location.html @@ -0,0 +1,20 @@ + + + + +Test window for top navigation by location tests + + + + + + + + diff --git a/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html b/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html new file mode 100644 index 0000000000..9a26bc80db --- /dev/null +++ b/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html @@ -0,0 +1,27 @@ + + + + +Test window for top navigation by location tests + + + + + + + diff --git a/docshell/test/iframesandbox/file_top_navigation_by_user_activation.html b/docshell/test/iframesandbox/file_top_navigation_by_user_activation.html new file mode 100644 index 0000000000..8454f1fac4 --- /dev/null +++ b/docshell/test/iframesandbox/file_top_navigation_by_user_activation.html @@ -0,0 +1,27 @@ + + + + +Test window for top navigation with user activation + + + + + + diff --git a/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html b/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html new file mode 100644 index 0000000000..b775579f28 --- /dev/null +++ b/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html @@ -0,0 +1,32 @@ + + + + + +Test window for top navigation with user activation + + + + diff --git a/docshell/test/iframesandbox/mochitest.ini b/docshell/test/iframesandbox/mochitest.ini new file mode 100644 index 0000000000..ee5a668fcc --- /dev/null +++ b/docshell/test/iframesandbox/mochitest.ini @@ -0,0 +1,30 @@ +[DEFAULT] +support-files = + file_child_navigation_by_location.html + file_marquee_event_handlers.html + file_other_auxiliary_navigation_by_location.html + file_our_auxiliary_navigation_by_location.html + file_parent_navigation_by_location.html + file_sibling_navigation_by_location.html + file_top_navigation_by_location.html + file_top_navigation_by_location_exotic.html + +[test_child_navigation_by_location.html] +[test_marquee_event_handlers.html] +skip-if = true # Bug 1455996 +[test_other_auxiliary_navigation_by_location.html] +tags = openwindow +[test_our_auxiliary_navigation_by_location.html] +tags = openwindow +[test_parent_navigation_by_location.html] +tags = openwindow +[test_sibling_navigation_by_location.html] +tags = openwindow +[test_top_navigation_by_location_exotic.html] +[test_top_navigation_by_location.html] +[test_top_navigation_by_user_activation.html] +support-files = + file_top_navigation_by_user_activation.html + file_top_navigation_by_user_activation_iframe.html +skip-if = + http3 diff --git a/docshell/test/iframesandbox/test_child_navigation_by_location.html b/docshell/test/iframesandbox/test_child_navigation_by_location.html new file mode 100644 index 0000000000..383320a02b --- /dev/null +++ b/docshell/test/iframesandbox/test_child_navigation_by_location.html @@ -0,0 +1,91 @@ + + + + + +Test for Bug 785310 - iframe sandbox child navigation by location tests + + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + + + + + + + diff --git a/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html new file mode 100644 index 0000000000..3440878db7 --- /dev/null +++ b/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html @@ -0,0 +1,80 @@ + + + + + +Test for Bug 785310 - iframe sandbox other auxiliary navigation by location tests + + + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + + + diff --git a/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html new file mode 100644 index 0000000000..1719f566a5 --- /dev/null +++ b/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html @@ -0,0 +1,84 @@ + + + + + +Test for Bug 785310 - iframe sandbox our auxiliary navigation by location tests + + + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + + + diff --git a/docshell/test/iframesandbox/test_parent_navigation_by_location.html b/docshell/test/iframesandbox/test_parent_navigation_by_location.html new file mode 100644 index 0000000000..ac6977a3f3 --- /dev/null +++ b/docshell/test/iframesandbox/test_parent_navigation_by_location.html @@ -0,0 +1,75 @@ + + + + + +Test for Bug 785310 - iframe sandbox parent navigation by location tests + + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + + + + diff --git a/docshell/test/iframesandbox/test_sibling_navigation_by_location.html b/docshell/test/iframesandbox/test_sibling_navigation_by_location.html new file mode 100644 index 0000000000..d7508d5748 --- /dev/null +++ b/docshell/test/iframesandbox/test_sibling_navigation_by_location.html @@ -0,0 +1,78 @@ + + + + + +Test for Bug 785310 - iframe sandbox sibling navigation by location tests + + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + + + + + diff --git a/docshell/test/iframesandbox/test_top_navigation_by_location.html b/docshell/test/iframesandbox/test_top_navigation_by_location.html new file mode 100644 index 0000000000..248f854bbf --- /dev/null +++ b/docshell/test/iframesandbox/test_top_navigation_by_location.html @@ -0,0 +1,167 @@ + + + + + +Test for Bug 785310 - iframe sandbox top navigation by location tests + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + diff --git a/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html b/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html new file mode 100644 index 0000000000..11b7c78699 --- /dev/null +++ b/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html @@ -0,0 +1,204 @@ + + + + + +Test for Bug 785310 - iframe sandbox top navigation by location via exotic means tests + + + + + +Mozilla Bug 785310 +

+
+Tests for Bug 785310 +
+ + diff --git a/docshell/test/iframesandbox/test_top_navigation_by_user_activation.html b/docshell/test/iframesandbox/test_top_navigation_by_user_activation.html new file mode 100644 index 0000000000..b462c54d7b --- /dev/null +++ b/docshell/test/iframesandbox/test_top_navigation_by_user_activation.html @@ -0,0 +1,74 @@ + + + + + +Iframe sandbox top navigation by user activation + + + + + +Mozilla Bug 1744321 +

+
+Tests for Bug 1744321 +
+ + diff --git a/docshell/test/mochitest/bug1422334_redirect.html b/docshell/test/mochitest/bug1422334_redirect.html new file mode 100644 index 0000000000..eec7fda2c7 --- /dev/null +++ b/docshell/test/mochitest/bug1422334_redirect.html @@ -0,0 +1,3 @@ + + You should never see this + diff --git a/docshell/test/mochitest/bug1422334_redirect.html^headers^ b/docshell/test/mochitest/bug1422334_redirect.html^headers^ new file mode 100644 index 0000000000..fbf2d1b745 --- /dev/null +++ b/docshell/test/mochitest/bug1422334_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved Temporarily +Location: ../navigation/blank.html?x=y diff --git a/docshell/test/mochitest/bug404548-subframe.html b/docshell/test/mochitest/bug404548-subframe.html new file mode 100644 index 0000000000..9a248b40b3 --- /dev/null +++ b/docshell/test/mochitest/bug404548-subframe.html @@ -0,0 +1,7 @@ + + + + + diff --git a/docshell/test/mochitest/bug404548-subframe_window.html b/docshell/test/mochitest/bug404548-subframe_window.html new file mode 100644 index 0000000000..82ea73ea83 --- /dev/null +++ b/docshell/test/mochitest/bug404548-subframe_window.html @@ -0,0 +1 @@ + diff --git a/docshell/test/mochitest/bug413310-post.sjs b/docshell/test/mochitest/bug413310-post.sjs new file mode 100644 index 0000000000..f87937ab56 --- /dev/null +++ b/docshell/test/mochitest/bug413310-post.sjs @@ -0,0 +1,10 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html"); + response.write( + "" + + request.method + + " " + + Date.now() + + "" + ); +} diff --git a/docshell/test/mochitest/bug413310-subframe.html b/docshell/test/mochitest/bug413310-subframe.html new file mode 100644 index 0000000000..bcff1886fd --- /dev/null +++ b/docshell/test/mochitest/bug413310-subframe.html @@ -0,0 +1,7 @@ + + + +
+
+ + diff --git a/docshell/test/mochitest/bug529119-window.html b/docshell/test/mochitest/bug529119-window.html new file mode 100644 index 0000000000..f1908835a7 --- /dev/null +++ b/docshell/test/mochitest/bug529119-window.html @@ -0,0 +1,7 @@ + + + +Test bug 529119, sub-window + + + diff --git a/docshell/test/mochitest/bug530396-noref.sjs b/docshell/test/mochitest/bug530396-noref.sjs new file mode 100644 index 0000000000..6a65882160 --- /dev/null +++ b/docshell/test/mochitest/bug530396-noref.sjs @@ -0,0 +1,22 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html"); + response.setHeader("Cache-Control", "no-cache"); + response.write(""); + } else { + response.write("window.parent.doNextStep();'>"); + } + + response.write(request.method + " " + Date.now()); + response.write(""); +} diff --git a/docshell/test/mochitest/bug530396-subframe.html b/docshell/test/mochitest/bug530396-subframe.html new file mode 100644 index 0000000000..be81b9f144 --- /dev/null +++ b/docshell/test/mochitest/bug530396-subframe.html @@ -0,0 +1,7 @@ + + + + bug530396-noref.sjs + bug530396-noref.sjs with new window + + diff --git a/docshell/test/mochitest/bug570341_recordevents.html b/docshell/test/mochitest/bug570341_recordevents.html new file mode 100644 index 0000000000..45b04866ec --- /dev/null +++ b/docshell/test/mochitest/bug570341_recordevents.html @@ -0,0 +1,21 @@ + + + + +This document collects time +for events related to the page load progress. + diff --git a/docshell/test/mochitest/bug668513_redirect.html b/docshell/test/mochitest/bug668513_redirect.html new file mode 100644 index 0000000000..1b8f66c631 --- /dev/null +++ b/docshell/test/mochitest/bug668513_redirect.html @@ -0,0 +1 @@ +This document is redirected to a blank document. diff --git a/docshell/test/mochitest/bug668513_redirect.html^headers^ b/docshell/test/mochitest/bug668513_redirect.html^headers^ new file mode 100644 index 0000000000..0e785833c6 --- /dev/null +++ b/docshell/test/mochitest/bug668513_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved Temporarily +Location: navigation/blank.html diff --git a/docshell/test/mochitest/bug691547_frame.html b/docshell/test/mochitest/bug691547_frame.html new file mode 100644 index 0000000000..00172f7119 --- /dev/null +++ b/docshell/test/mochitest/bug691547_frame.html @@ -0,0 +1,12 @@ + + + + + Test for Bug 691547 + + + + + diff --git a/docshell/test/mochitest/clicker.html b/docshell/test/mochitest/clicker.html new file mode 100644 index 0000000000..b655e27ea5 --- /dev/null +++ b/docshell/test/mochitest/clicker.html @@ -0,0 +1,7 @@ + + diff --git a/docshell/test/mochitest/double_submit.sjs b/docshell/test/mochitest/double_submit.sjs new file mode 100644 index 0000000000..487150d456 --- /dev/null +++ b/docshell/test/mochitest/double_submit.sjs @@ -0,0 +1,79 @@ +"use strict"; + +let self = this; + +let { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function log(str) { + // dump(`LOG: ${str}\n`); +} + +function readStream(inputStream) { + let available = 0; + let result = []; + while ((available = inputStream.available()) > 0) { + result.push(inputStream.readBytes(available)); + } + + return result.join(""); +} + +function now() { + return Date.now(); +} + +async function handleRequest(request, response) { + log("Get query parameters"); + Cu.importGlobalProperties(["URLSearchParams"]); + let params = new URLSearchParams(request.queryString); + + let start = now(); + let delay = parseInt(params.get("delay")) || 0; + log(`Delay for ${delay}`); + + let message = "good"; + if (request.method !== "POST") { + message = "bad"; + } else { + log("Read POST body"); + let body = new URLSearchParams( + readStream(new BinaryInputStream(request.bodyInputStream)) + ); + message = body.get("token") || "bad"; + log(`The result was ${message}`); + } + + let body = ` + `; + + // Sieze power from the response to allow manually transmitting data at any + // rate we want, so we can delay transmitting headers. + response.seizePower(); + + log(`Writing HTTP status line at ${now() - start}`); + response.write("HTTP/1.1 200 OK\r\n"); + + await new Promise(resolve => setTimeout(() => resolve(), delay)); + + log(`Delay completed at ${now() - start}`); + response.write("Content-Type: text/html\r\n"); + response.write(`Content-Length: ${body.length}\r\n`); + response.write("\r\n"); + response.write(body); + response.finish(); + + log("Finished"); +} diff --git a/docshell/test/mochitest/dummy_page.html b/docshell/test/mochitest/dummy_page.html new file mode 100644 index 0000000000..59bf2a5f8f --- /dev/null +++ b/docshell/test/mochitest/dummy_page.html @@ -0,0 +1,6 @@ + + + + just a dummy html file + + diff --git a/docshell/test/mochitest/file_anchor_scroll_after_document_open.html b/docshell/test/mochitest/file_anchor_scroll_after_document_open.html new file mode 100644 index 0000000000..7903380eac --- /dev/null +++ b/docshell/test/mochitest/file_anchor_scroll_after_document_open.html @@ -0,0 +1,15 @@ + + diff --git a/docshell/test/mochitest/file_bfcache_plus_hash_1.html b/docshell/test/mochitest/file_bfcache_plus_hash_1.html new file mode 100644 index 0000000000..199f6003e0 --- /dev/null +++ b/docshell/test/mochitest/file_bfcache_plus_hash_1.html @@ -0,0 +1,24 @@ + + Popup 1 + + diff --git a/docshell/test/mochitest/file_bfcache_plus_hash_2.html b/docshell/test/mochitest/file_bfcache_plus_hash_2.html new file mode 100644 index 0000000000..c27d4eaa3b --- /dev/null +++ b/docshell/test/mochitest/file_bfcache_plus_hash_2.html @@ -0,0 +1,17 @@ + + Popup 2 + + diff --git a/docshell/test/mochitest/file_bug1121701_1.html b/docshell/test/mochitest/file_bug1121701_1.html new file mode 100644 index 0000000000..62c58495f7 --- /dev/null +++ b/docshell/test/mochitest/file_bug1121701_1.html @@ -0,0 +1,29 @@ + diff --git a/docshell/test/mochitest/file_bug1121701_2.html b/docshell/test/mochitest/file_bug1121701_2.html new file mode 100644 index 0000000000..6cec88cd5d --- /dev/null +++ b/docshell/test/mochitest/file_bug1121701_2.html @@ -0,0 +1,23 @@ + diff --git a/docshell/test/mochitest/file_bug1151421.html b/docshell/test/mochitest/file_bug1151421.html new file mode 100644 index 0000000000..7bb8c8f363 --- /dev/null +++ b/docshell/test/mochitest/file_bug1151421.html @@ -0,0 +1,19 @@ + + + + + + +
+
content
+
+ + + diff --git a/docshell/test/mochitest/file_bug1186774.html b/docshell/test/mochitest/file_bug1186774.html new file mode 100644 index 0000000000..9af95b09bd --- /dev/null +++ b/docshell/test/mochitest/file_bug1186774.html @@ -0,0 +1 @@ +
diff --git a/docshell/test/mochitest/file_bug1450164.html b/docshell/test/mochitest/file_bug1450164.html new file mode 100644 index 0000000000..55e32ce93d --- /dev/null +++ b/docshell/test/mochitest/file_bug1450164.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/docshell/test/mochitest/file_bug1729662.html b/docshell/test/mochitest/file_bug1729662.html new file mode 100644 index 0000000000..f5710e1c49 --- /dev/null +++ b/docshell/test/mochitest/file_bug1729662.html @@ -0,0 +1,8 @@ + diff --git a/docshell/test/mochitest/file_bug1740516_1.html b/docshell/test/mochitest/file_bug1740516_1.html new file mode 100644 index 0000000000..ac8ca71d93 --- /dev/null +++ b/docshell/test/mochitest/file_bug1740516_1.html @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug1740516_1_inner.html b/docshell/test/mochitest/file_bug1740516_1_inner.html new file mode 100644 index 0000000000..159c6bde5a --- /dev/null +++ b/docshell/test/mochitest/file_bug1740516_1_inner.html @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug1740516_2.html b/docshell/test/mochitest/file_bug1740516_2.html new file mode 100644 index 0000000000..2dc714feef --- /dev/null +++ b/docshell/test/mochitest/file_bug1740516_2.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug1741132.html b/docshell/test/mochitest/file_bug1741132.html new file mode 100644 index 0000000000..d863b9f015 --- /dev/null +++ b/docshell/test/mochitest/file_bug1741132.html @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug1742865.sjs b/docshell/test/mochitest/file_bug1742865.sjs new file mode 100644 index 0000000000..950c30ecd2 --- /dev/null +++ b/docshell/test/mochitest/file_bug1742865.sjs @@ -0,0 +1,77 @@ +Cu.importGlobalProperties(["URLSearchParams"]); + +function handleRequest(request, response) { + if (request.queryString == "reset") { + setState("index", "0"); + response.setStatusLine(request.httpVersion, 200, "Ok"); + response.write("Reset"); + return; + } + + let refresh = ""; + let index = Number(getState("index")); + // index == 0 First load, returns first meta refresh + // index == 1 Second load, caused by first meta refresh, returns second meta refresh + // index == 2 Third load, caused by second meta refresh, doesn't return a meta refresh + let query = new URLSearchParams(request.queryString); + if (index < 2) { + refresh = query.get("seconds"); + if (query.get("crossOrigin") == "true") { + const hosts = ["example.org", "example.com"]; + + let url = `${request.scheme}://${hosts[index]}${request.path}?${request.queryString}`; + refresh += `; url=${url}`; + } + refresh = ``; + } + // We want to scroll for the first load, and check that the meta refreshes keep the same + // scroll position. + let scroll = index == 0 ? `scrollTo(0, ${query.get("scrollTo")});` : ""; + + setState("index", String(index + 1)); + + response.write( + ` + + + + + ${refresh} + + + + +
+

+

+ +` + ); +} diff --git a/docshell/test/mochitest/file_bug1742865_outer.sjs b/docshell/test/mochitest/file_bug1742865_outer.sjs new file mode 100644 index 0000000000..bad8b23f00 --- /dev/null +++ b/docshell/test/mochitest/file_bug1742865_outer.sjs @@ -0,0 +1,25 @@ +Cu.importGlobalProperties(["URLSearchParams"]); + +function handleRequest(request, response) { + response.write( + ` + + + + + + + + +` + ); +} diff --git a/docshell/test/mochitest/file_bug1743353.html b/docshell/test/mochitest/file_bug1743353.html new file mode 100644 index 0000000000..c08f8d143f --- /dev/null +++ b/docshell/test/mochitest/file_bug1743353.html @@ -0,0 +1,37 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug1747033.sjs b/docshell/test/mochitest/file_bug1747033.sjs new file mode 100644 index 0000000000..14401101b2 --- /dev/null +++ b/docshell/test/mochitest/file_bug1747033.sjs @@ -0,0 +1,110 @@ +"use strict"; + +const BOUNDARY = "BOUNDARY"; + +// waitForPageShow should be false if this is for multipart/x-mixed-replace +// and it's not the last part, Gecko doesn't fire pageshow for those parts. +function documentString(waitForPageShow = true) { + return ` + + + + + +`; +} + +function boundary(last = false) { + let b = `--${BOUNDARY}`; + if (last) { + b += "--"; + } + return b + "\n"; +} + +function sendMultipart(response, last = false) { + setState("sendMore", ""); + + response.write(`Content-Type: text/html + +${documentString(last)} +`); + response.write(boundary(last)); +} + +function shouldSendMore() { + return new Promise(resolve => { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + () => { + let sendMore = getState("sendMore"); + if (sendMore !== "") { + timer.cancel(); + resolve(sendMore); + } + }, + 100, + Ci.nsITimer.TYPE_REPEATING_SLACK + ); + }); +} + +async function handleRequest(request, response) { + if (request.queryString == "") { + // This is for non-multipart/x-mixed-replace loads. + response.write(documentString()); + return; + } + + if (request.queryString == "sendNextPart") { + setState("sendMore", "next"); + return; + } + + if (request.queryString == "sendLastPart") { + setState("sendMore", "last"); + return; + } + + response.processAsync(); + + response.setHeader( + "Content-Type", + `multipart/x-mixed-replace; boundary=${BOUNDARY}`, + false + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + + response.write(boundary()); + sendMultipart(response); + while ((await shouldSendMore("sendMore")) !== "last") { + sendMultipart(response); + } + sendMultipart(response, true); + response.finish(); +} diff --git a/docshell/test/mochitest/file_bug1773192_1.html b/docshell/test/mochitest/file_bug1773192_1.html new file mode 100644 index 0000000000..42d5b3fced --- /dev/null +++ b/docshell/test/mochitest/file_bug1773192_1.html @@ -0,0 +1,13 @@ + diff --git a/docshell/test/mochitest/file_bug1773192_2.html b/docshell/test/mochitest/file_bug1773192_2.html new file mode 100644 index 0000000000..3b1e5bcb28 --- /dev/null +++ b/docshell/test/mochitest/file_bug1773192_2.html @@ -0,0 +1,13 @@ + + + + + + +
+ + + diff --git a/docshell/test/mochitest/file_bug1773192_3.sjs b/docshell/test/mochitest/file_bug1773192_3.sjs new file mode 100644 index 0000000000..ce889c7035 --- /dev/null +++ b/docshell/test/mochitest/file_bug1773192_3.sjs @@ -0,0 +1,3 @@ +function handleRequest(request, response) { + response.write(""); +} diff --git a/docshell/test/mochitest/file_bug385434_1.html b/docshell/test/mochitest/file_bug385434_1.html new file mode 100644 index 0000000000..5c951f1fa6 --- /dev/null +++ b/docshell/test/mochitest/file_bug385434_1.html @@ -0,0 +1,29 @@ + + + + + + + +link1 + +
+link2 + + diff --git a/docshell/test/mochitest/file_bug385434_2.html b/docshell/test/mochitest/file_bug385434_2.html new file mode 100644 index 0000000000..4aa5ef82b8 --- /dev/null +++ b/docshell/test/mochitest/file_bug385434_2.html @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug385434_3.html b/docshell/test/mochitest/file_bug385434_3.html new file mode 100644 index 0000000000..34dd68ef45 --- /dev/null +++ b/docshell/test/mochitest/file_bug385434_3.html @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug475636.sjs b/docshell/test/mochitest/file_bug475636.sjs new file mode 100644 index 0000000000..cba9a968d6 --- /dev/null +++ b/docshell/test/mochitest/file_bug475636.sjs @@ -0,0 +1,97 @@ +let jsURL = + "javascript:" + + escape( + 'window.parent.postMessage("JS uri ran", "*");\ +return \'\ +\'' + ); +let dataURL = + "data:text/html," + + escape( + '\ +' + ); + +let tests = [ + // Plain document should work as normal + '\ +', + + // refresh to plain doc + { refresh: "file_bug475636.sjs?1", doc: "" }, + + // meta-refresh to plain doc + '\ +\ + \ +', + + // refresh to data url + { refresh: dataURL, doc: "" }, + + // meta-refresh to data url + '\ +\ + \ +', + + // refresh to js url should not be followed + { + refresh: jsURL, + doc: '\ +', + }, + + // meta refresh to js url should not be followed + '\ +\ + \ +\ +', +]; + +function handleRequest(request, response) { + dump("@@@@@@@@@hi there: " + request.queryString + "\n"); + let test = tests[parseInt(request.queryString, 10) - 1]; + response.setHeader("Content-Type", "text/html"); + + if (!test) { + response.write(''); + } else if (typeof test == "string") { + response.write(test); + } else if (test.refresh) { + response.setHeader("Refresh", "0; url=" + test.refresh); + response.write(test.doc); + } +} diff --git a/docshell/test/mochitest/file_bug509055.html b/docshell/test/mochitest/file_bug509055.html new file mode 100644 index 0000000000..ac30876bbf --- /dev/null +++ b/docshell/test/mochitest/file_bug509055.html @@ -0,0 +1,9 @@ + + + + Test inner frame for bug 509055 + + + file_bug509055.html + + diff --git a/docshell/test/mochitest/file_bug511449.html b/docshell/test/mochitest/file_bug511449.html new file mode 100644 index 0000000000..637732dbbf --- /dev/null +++ b/docshell/test/mochitest/file_bug511449.html @@ -0,0 +1,6 @@ + +Used in test for bug 511449 + + diff --git a/docshell/test/mochitest/file_bug540462.html b/docshell/test/mochitest/file_bug540462.html new file mode 100644 index 0000000000..ab8c07eba5 --- /dev/null +++ b/docshell/test/mochitest/file_bug540462.html @@ -0,0 +1,25 @@ + + + + + + Test for bug 540462 + + diff --git a/docshell/test/mochitest/file_bug580069_1.html b/docshell/test/mochitest/file_bug580069_1.html new file mode 100644 index 0000000000..7ab4610334 --- /dev/null +++ b/docshell/test/mochitest/file_bug580069_1.html @@ -0,0 +1,8 @@ + + +file_bug580069_1.html + +
+ + + diff --git a/docshell/test/mochitest/file_bug580069_2.sjs b/docshell/test/mochitest/file_bug580069_2.sjs new file mode 100644 index 0000000000..ff03c74e68 --- /dev/null +++ b/docshell/test/mochitest/file_bug580069_2.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html", false); + response.write( + "file_bug580069_2.sjs" + ); +} diff --git a/docshell/test/mochitest/file_bug590573_1.html b/docshell/test/mochitest/file_bug590573_1.html new file mode 100644 index 0000000000..b859ca7469 --- /dev/null +++ b/docshell/test/mochitest/file_bug590573_1.html @@ -0,0 +1,7 @@ + + + +
This is a very tall div.
+ + + diff --git a/docshell/test/mochitest/file_bug590573_2.html b/docshell/test/mochitest/file_bug590573_2.html new file mode 100644 index 0000000000..5f9ca22be4 --- /dev/null +++ b/docshell/test/mochitest/file_bug590573_2.html @@ -0,0 +1,8 @@ + + + +
The second page also has a big div.
+ + + diff --git a/docshell/test/mochitest/file_bug598895_1.html b/docshell/test/mochitest/file_bug598895_1.html new file mode 100644 index 0000000000..d21f2b4a5d --- /dev/null +++ b/docshell/test/mochitest/file_bug598895_1.html @@ -0,0 +1 @@ +Should show diff --git a/docshell/test/mochitest/file_bug598895_2.html b/docshell/test/mochitest/file_bug598895_2.html new file mode 100644 index 0000000000..680c9bf22b --- /dev/null +++ b/docshell/test/mochitest/file_bug598895_2.html @@ -0,0 +1 @@ + diff --git a/docshell/test/mochitest/file_bug634834.html b/docshell/test/mochitest/file_bug634834.html new file mode 100644 index 0000000000..3ff0897451 --- /dev/null +++ b/docshell/test/mochitest/file_bug634834.html @@ -0,0 +1,5 @@ + + +Nothing to see here; just an empty page. + + diff --git a/docshell/test/mochitest/file_bug637644_1.html b/docshell/test/mochitest/file_bug637644_1.html new file mode 100644 index 0000000000..d21f2b4a5d --- /dev/null +++ b/docshell/test/mochitest/file_bug637644_1.html @@ -0,0 +1 @@ +Should show diff --git a/docshell/test/mochitest/file_bug637644_2.html b/docshell/test/mochitest/file_bug637644_2.html new file mode 100644 index 0000000000..680c9bf22b --- /dev/null +++ b/docshell/test/mochitest/file_bug637644_2.html @@ -0,0 +1 @@ + diff --git a/docshell/test/mochitest/file_bug640387.html b/docshell/test/mochitest/file_bug640387.html new file mode 100644 index 0000000000..3a939fb41e --- /dev/null +++ b/docshell/test/mochitest/file_bug640387.html @@ -0,0 +1,26 @@ + + + + + +Not much to see here... + + diff --git a/docshell/test/mochitest/file_bug653741.html b/docshell/test/mochitest/file_bug653741.html new file mode 100644 index 0000000000..3202b52573 --- /dev/null +++ b/docshell/test/mochitest/file_bug653741.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug660404 b/docshell/test/mochitest/file_bug660404 new file mode 100644 index 0000000000..ed773420ef --- /dev/null +++ b/docshell/test/mochitest/file_bug660404 @@ -0,0 +1,13 @@ +--testingtesting +Content-Type: text/html + + +--testingtesting-- diff --git a/docshell/test/mochitest/file_bug660404-1.html b/docshell/test/mochitest/file_bug660404-1.html new file mode 100644 index 0000000000..878bd80426 --- /dev/null +++ b/docshell/test/mochitest/file_bug660404-1.html @@ -0,0 +1,12 @@ + diff --git a/docshell/test/mochitest/file_bug660404^headers^ b/docshell/test/mochitest/file_bug660404^headers^ new file mode 100644 index 0000000000..5c821f3f48 --- /dev/null +++ b/docshell/test/mochitest/file_bug660404^headers^ @@ -0,0 +1 @@ +Content-Type: multipart/x-mixed-replace; boundary="testingtesting" diff --git a/docshell/test/mochitest/file_bug662170.html b/docshell/test/mochitest/file_bug662170.html new file mode 100644 index 0000000000..3202b52573 --- /dev/null +++ b/docshell/test/mochitest/file_bug662170.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_bug668513.html b/docshell/test/mochitest/file_bug668513.html new file mode 100644 index 0000000000..ae417a35bd --- /dev/null +++ b/docshell/test/mochitest/file_bug668513.html @@ -0,0 +1,101 @@ + + + + Test file for Bug 668513 + + + +
+ +
+ + diff --git a/docshell/test/mochitest/file_bug669671.sjs b/docshell/test/mochitest/file_bug669671.sjs new file mode 100644 index 0000000000..5871419de8 --- /dev/null +++ b/docshell/test/mochitest/file_bug669671.sjs @@ -0,0 +1,17 @@ +function handleRequest(request, response) { + var count = parseInt(getState("count")); + if (!count || request.queryString == "countreset") { + count = 0; + } + + setState("count", count + 1 + ""); + + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "max-age=0"); + response.write( + '" + + count + + "" + ); +} diff --git a/docshell/test/mochitest/file_bug675587.html b/docshell/test/mochitest/file_bug675587.html new file mode 100644 index 0000000000..842ab9ae79 --- /dev/null +++ b/docshell/test/mochitest/file_bug675587.html @@ -0,0 +1 @@ + diff --git a/docshell/test/mochitest/file_bug680257.html b/docshell/test/mochitest/file_bug680257.html new file mode 100644 index 0000000000..ff480e96a5 --- /dev/null +++ b/docshell/test/mochitest/file_bug680257.html @@ -0,0 +1,16 @@ + + + + + + + + +link +link2 + + + diff --git a/docshell/test/mochitest/file_bug703855.html b/docshell/test/mochitest/file_bug703855.html new file mode 100644 index 0000000000..fe15b6e3df --- /dev/null +++ b/docshell/test/mochitest/file_bug703855.html @@ -0,0 +1,2 @@ + + diff --git a/docshell/test/mochitest/file_bug728939.html b/docshell/test/mochitest/file_bug728939.html new file mode 100644 index 0000000000..1cd52a44e1 --- /dev/null +++ b/docshell/test/mochitest/file_bug728939.html @@ -0,0 +1,3 @@ + +file_bug728939 + diff --git a/docshell/test/mochitest/file_close_onpagehide1.html b/docshell/test/mochitest/file_close_onpagehide1.html new file mode 100644 index 0000000000..ccf3b625a1 --- /dev/null +++ b/docshell/test/mochitest/file_close_onpagehide1.html @@ -0,0 +1,5 @@ + diff --git a/docshell/test/mochitest/file_close_onpagehide2.html b/docshell/test/mochitest/file_close_onpagehide2.html new file mode 100644 index 0000000000..a8e9479f47 --- /dev/null +++ b/docshell/test/mochitest/file_close_onpagehide2.html @@ -0,0 +1,5 @@ +; diff --git a/docshell/test/mochitest/file_compressed_multipart b/docshell/test/mochitest/file_compressed_multipart new file mode 100644 index 0000000000..3c56226951 Binary files /dev/null and b/docshell/test/mochitest/file_compressed_multipart differ diff --git a/docshell/test/mochitest/file_compressed_multipart^headers^ b/docshell/test/mochitest/file_compressed_multipart^headers^ new file mode 100644 index 0000000000..9376927812 --- /dev/null +++ b/docshell/test/mochitest/file_compressed_multipart^headers^ @@ -0,0 +1,2 @@ +Content-Type: multipart/x-mixed-replace; boundary="testingtesting" +Content-Encoding: gzip diff --git a/docshell/test/mochitest/file_content_javascript_loads_frame.html b/docshell/test/mochitest/file_content_javascript_loads_frame.html new file mode 100644 index 0000000000..9e2851aa8b --- /dev/null +++ b/docshell/test/mochitest/file_content_javascript_loads_frame.html @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_content_javascript_loads_root.html b/docshell/test/mochitest/file_content_javascript_loads_root.html new file mode 100644 index 0000000000..b9f2c1faa7 --- /dev/null +++ b/docshell/test/mochitest/file_content_javascript_loads_root.html @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/docshell/test/mochitest/file_form_restoration_no_store.html b/docshell/test/mochitest/file_form_restoration_no_store.html new file mode 100644 index 0000000000..6e8756a693 --- /dev/null +++ b/docshell/test/mochitest/file_form_restoration_no_store.html @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/docshell/test/mochitest/file_form_restoration_no_store.html^headers^ b/docshell/test/mochitest/file_form_restoration_no_store.html^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/docshell/test/mochitest/file_form_restoration_no_store.html^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/docshell/test/mochitest/file_framedhistoryframes.html b/docshell/test/mochitest/file_framedhistoryframes.html new file mode 100644 index 0000000000..314f9c72d8 --- /dev/null +++ b/docshell/test/mochitest/file_framedhistoryframes.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/docshell/test/mochitest/file_load_during_reload.html b/docshell/test/mochitest/file_load_during_reload.html new file mode 100644 index 0000000000..600d5c1728 --- /dev/null +++ b/docshell/test/mochitest/file_load_during_reload.html @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/docshell/test/mochitest/file_pushState_after_document_open.html b/docshell/test/mochitest/file_pushState_after_document_open.html new file mode 100644 index 0000000000..97a6954f2e --- /dev/null +++ b/docshell/test/mochitest/file_pushState_after_document_open.html @@ -0,0 +1,11 @@ + + diff --git a/docshell/test/mochitest/file_redirect_history.html b/docshell/test/mochitest/file_redirect_history.html new file mode 100644 index 0000000000..3971faf4fd --- /dev/null +++ b/docshell/test/mochitest/file_redirect_history.html @@ -0,0 +1,18 @@ + + + + + +
+ +
+ + diff --git a/docshell/test/mochitest/form_submit.sjs b/docshell/test/mochitest/form_submit.sjs new file mode 100644 index 0000000000..1a1fa5d89c --- /dev/null +++ b/docshell/test/mochitest/form_submit.sjs @@ -0,0 +1,40 @@ +"use strict"; + +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +const BinaryOutputStream = CC( + "@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream" +); + +function log(str) { + // dump(`LOG: ${str}\n`); +} + +async function handleRequest(request, response) { + if (request.method !== "POST") { + throw new Error("Expected a post request"); + } else { + log("Reading request"); + let available = 0; + let inputStream = new BinaryInputStream(request.bodyInputStream); + while ((available = inputStream.available()) > 0) { + log(inputStream.readBytes(available)); + } + } + + log("Setting Headers"); + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "200", "OK"); + log("Writing body"); + response.write( + '' + ); + log("Done"); +} diff --git a/docshell/test/mochitest/form_submit_redirect.sjs b/docshell/test/mochitest/form_submit_redirect.sjs new file mode 100644 index 0000000000..dbc32b9643 --- /dev/null +++ b/docshell/test/mochitest/form_submit_redirect.sjs @@ -0,0 +1,15 @@ +"use strict"; + +Cu.importGlobalProperties(["URLSearchParams"]); + +async function handleRequest(request, response) { + if (request.method !== "POST") { + throw new Error("Expected a post request"); + } else { + let params = new URLSearchParams(request.queryString); + let redirect = params.get("redirectTo"); + + response.setStatusLine(request.httpVersion, 302, "Moved Temporarily"); + response.setHeader("Location", redirect); + } +} diff --git a/docshell/test/mochitest/historyframes.html b/docshell/test/mochitest/historyframes.html new file mode 100644 index 0000000000..31f46a5071 --- /dev/null +++ b/docshell/test/mochitest/historyframes.html @@ -0,0 +1,176 @@ + + + + + Test for Bug 602256 + + +Mozilla Bug 602256 +
+ +
+
+
+
+ + diff --git a/docshell/test/mochitest/mochitest.ini b/docshell/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..598d629673 --- /dev/null +++ b/docshell/test/mochitest/mochitest.ini @@ -0,0 +1,209 @@ +[DEFAULT] +support-files = + bug404548-subframe.html + bug404548-subframe_window.html + bug413310-post.sjs + bug413310-subframe.html + bug529119-window.html + bug570341_recordevents.html + bug668513_redirect.html + bug668513_redirect.html^headers^ + bug691547_frame.html + dummy_page.html + file_anchor_scroll_after_document_open.html + file_bfcache_plus_hash_1.html + file_bfcache_plus_hash_2.html + file_bug385434_1.html + file_bug385434_2.html + file_bug385434_3.html + file_bug475636.sjs + file_bug509055.html + file_bug540462.html + file_bug580069_1.html + file_bug580069_2.sjs + file_bug598895_1.html + file_bug598895_2.html + file_bug590573_1.html + file_bug590573_2.html + file_bug634834.html + file_bug637644_1.html + file_bug637644_2.html + file_bug640387.html + file_bug653741.html + file_bug660404 + file_bug660404^headers^ + file_bug660404-1.html + file_bug662170.html + file_bug669671.sjs + file_bug680257.html + file_bug703855.html + file_bug728939.html + file_bug1121701_1.html + file_bug1121701_2.html + file_bug1186774.html + file_bug1151421.html + file_bug1450164.html + file_close_onpagehide1.html + file_close_onpagehide2.html + file_compressed_multipart + file_compressed_multipart^headers^ + file_pushState_after_document_open.html + historyframes.html + ping.html + start_historyframe.html + url1_historyframe.html + url2_historyframe.html + +[test_anchor_scroll_after_document_open.html] +[test_bfcache_plus_hash.html] +[test_bug1422334.html] +support-files = + bug1422334_redirect.html + bug1422334_redirect.html^headers^ + !/docshell/test/navigation/blank.html +[test_bug385434.html] +[test_bug387979.html] +[test_bug402210.html] +skip-if = + http3 +[test_bug404548.html] +[test_bug413310.html] +skip-if = true +# Disabled for too many intermittent failures (bug 719186) +[test_bug475636.html] +[test_bug509055.html] +[test_bug511449.html] +skip-if = toolkit != "cocoa" || headless # Headless: bug 1410525 +support-files = file_bug511449.html +[test_bug529119-1.html] +skip-if = + fission && os == "android" # Bug 1827321 +[test_bug529119-2.html] +skip-if = + http3 + fission && os == "android" # Bug 1827321 +[test_bug530396.html] +support-files = bug530396-noref.sjs bug530396-subframe.html +skip-if = + http3 +[test_bug540462.html] +skip-if = toolkit == 'android' && debug +[test_bug551225.html] +[test_bug570341.html] +skip-if = (verify && !debug && (os == 'win')) +[test_bug580069.html] +skip-if = (verify && !debug && (os == 'win')) +[test_bug590573.html] +[test_bug598895.html] +[test_bug634834.html] +skip-if = + http3 +[test_bug637644.html] +[test_bug640387_1.html] +[test_bug640387_2.html] +[test_bug653741.html] +[test_bug660404.html] +[test_bug662170.html] +[test_bug668513.html] +support-files = file_bug668513.html +[test_bug669671.html] +[test_bug675587.html] +support-files = file_bug675587.html +[test_bug680257.html] +[test_bug691547.html] +[test_bug694612.html] +[test_bug703855.html] +[test_bug728939.html] +[test_bug797909.html] +[test_bug1045096.html] +[test_bug1121701.html] +[test_bug1151421.html] +[test_bug1186774.html] +[test_bug1450164.html] +[test_bug1507702.html] +[test_bug1645781.html] +support-files = + form_submit.sjs +skip-if = + http3 +[test_bug1729662.html] +support-files = + file_bug1729662.html +[test_bug1740516.html] +support-files = + file_bug1740516_1.html + file_bug1740516_1_inner.html + file_bug1740516_2.html +[test_form_restoration.html] +support-files = + file_form_restoration_no_store.html + file_form_restoration_no_store.html^headers^ +[test_bug1741132.html] +support-files = + file_bug1741132.html +skip-if = toolkit == "android" && !sessionHistoryInParent +[test_bug1742865.html] +support-files = + file_bug1742865.sjs + file_bug1742865_outer.sjs +skip-if = + toolkit == "android" && debug && fission && verify # Bug 1745937 + http3 +[test_bug1743353.html] +support-files = + file_bug1743353.html +[test_bug1747033.html] +support-files = + file_bug1747033.sjs +skip-if = + http3 +[test_bug1773192.html] +support-files = + file_bug1773192_1.html + file_bug1773192_2.html + file_bug1773192_3.sjs +[test_close_onpagehide_by_history_back.html] +[test_close_onpagehide_by_window_close.html] +[test_compressed_multipart.html] +[test_content_javascript_loads.html] +support-files = + file_content_javascript_loads_root.html + file_content_javascript_loads_frame.html +skip-if = + http3 +[test_forceinheritprincipal_overrule_owner.html] +skip-if = + http3 +[test_framedhistoryframes.html] +support-files = file_framedhistoryframes.html +skip-if = + http3 +[test_load_during_reload.html] +support-files = file_load_during_reload.html +[test_pushState_after_document_open.html] +[test_navigate_after_pagehide.html] +skip-if = + http3 +[test_redirect_history.html] +support-files = + file_redirect_history.html + form_submit_redirect.sjs +skip-if = + http3 +[test_windowedhistoryframes.html] +skip-if = + (!debug && os == 'android') # Bug 1573892 + http3 +[test_triggeringprincipal_location_seturi.html] +skip-if = + http3 +[test_double_submit.html] +support-files = + clicker.html + double_submit.sjs +skip-if = + http3 +[test_iframe_srcdoc_to_remote.html] +skip-if = + http3 +[test_javascript_sandboxed_popup.html] diff --git a/docshell/test/mochitest/ping.html b/docshell/test/mochitest/ping.html new file mode 100644 index 0000000000..7d84560dd1 --- /dev/null +++ b/docshell/test/mochitest/ping.html @@ -0,0 +1,6 @@ + + diff --git a/docshell/test/mochitest/start_historyframe.html b/docshell/test/mochitest/start_historyframe.html new file mode 100644 index 0000000000..a791af4e64 --- /dev/null +++ b/docshell/test/mochitest/start_historyframe.html @@ -0,0 +1 @@ +

Start

diff --git a/docshell/test/mochitest/test_anchor_scroll_after_document_open.html b/docshell/test/mochitest/test_anchor_scroll_after_document_open.html new file mode 100644 index 0000000000..5309f6cf19 --- /dev/null +++ b/docshell/test/mochitest/test_anchor_scroll_after_document_open.html @@ -0,0 +1,55 @@ + + + + + + Test for Bug 881487 + + + + + +Mozilla Bug 881487 +

+ + +

+ +
+
+ + diff --git a/docshell/test/mochitest/test_bfcache_plus_hash.html b/docshell/test/mochitest/test_bfcache_plus_hash.html new file mode 100644 index 0000000000..cb5bc06f21 --- /dev/null +++ b/docshell/test/mochitest/test_bfcache_plus_hash.html @@ -0,0 +1,153 @@ + + + + + Test for Bug 646641 + + + + + +Mozilla Bug 646641 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug1045096.html b/docshell/test/mochitest/test_bug1045096.html new file mode 100644 index 0000000000..2df2232b7e --- /dev/null +++ b/docshell/test/mochitest/test_bug1045096.html @@ -0,0 +1,29 @@ + + + + + + Test for Bug 1045096 + + + + +Mozilla Bug 1045096 +

+ +
+
+ + + diff --git a/docshell/test/mochitest/test_bug1121701.html b/docshell/test/mochitest/test_bug1121701.html new file mode 100644 index 0000000000..cd0b2529d4 --- /dev/null +++ b/docshell/test/mochitest/test_bug1121701.html @@ -0,0 +1,108 @@ + + + + + + Test for Bug 1121701 + + + + + +Mozilla Bug 1121701 +

+ +
+
+ + diff --git a/docshell/test/mochitest/test_bug1151421.html b/docshell/test/mochitest/test_bug1151421.html new file mode 100644 index 0000000000..0738ee783e --- /dev/null +++ b/docshell/test/mochitest/test_bug1151421.html @@ -0,0 +1,61 @@ + + + + + Test for Bug 1151421 + + + + +Mozilla Bug 1151421 + + + + +
+ + + + diff --git a/docshell/test/mochitest/test_bug1186774.html b/docshell/test/mochitest/test_bug1186774.html new file mode 100644 index 0000000000..9ec56baf11 --- /dev/null +++ b/docshell/test/mochitest/test_bug1186774.html @@ -0,0 +1,51 @@ + + + + + + Test for Bug 1186774 + + + + + +Mozilla Bug 1186774 +

+ +
+
+ + diff --git a/docshell/test/mochitest/test_bug1422334.html b/docshell/test/mochitest/test_bug1422334.html new file mode 100644 index 0000000000..b525ae1d9c --- /dev/null +++ b/docshell/test/mochitest/test_bug1422334.html @@ -0,0 +1,40 @@ + + + + + Ensure that reload after replaceState after 3xx redirect does the right thing. + + + + + +

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1450164.html b/docshell/test/mochitest/test_bug1450164.html
new file mode 100644
index 0000000000..546a988394
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1450164.html
@@ -0,0 +1,31 @@
+
+
+  
+  
+    
+    Test for Bug 1450164
+    
+    
+    
+  
+  
+    Mozilla Bug 1450164
+  
+
diff --git a/docshell/test/mochitest/test_bug1507702.html b/docshell/test/mochitest/test_bug1507702.html
new file mode 100644
index 0000000000..fd88ee60a5
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1507702.html
@@ -0,0 +1,57 @@
+
+
+
+
+  
+  Test for Bug 1507702
+  
+  
+  
+
+
+Mozilla Bug 1507702
+
+
+
+
+
+
+
diff --git a/docshell/test/mochitest/test_bug1645781.html b/docshell/test/mochitest/test_bug1645781.html
new file mode 100644
index 0000000000..6cf676b7a9
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1645781.html
@@ -0,0 +1,90 @@
+
+
+  
+    Test for Bug 1590762
+    
+    
+    
+  
+  
+    
+ + +
+ + + diff --git a/docshell/test/mochitest/test_bug1729662.html b/docshell/test/mochitest/test_bug1729662.html new file mode 100644 index 0000000000..ec43508494 --- /dev/null +++ b/docshell/test/mochitest/test_bug1729662.html @@ -0,0 +1,76 @@ + + + + + Test back/forward after pushState + + + + + +

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1740516.html b/docshell/test/mochitest/test_bug1740516.html
new file mode 100644
index 0000000000..b54932c736
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1740516.html
@@ -0,0 +1,79 @@
+
+
+
+  
+  Test pageshow event order for iframe
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1741132.html b/docshell/test/mochitest/test_bug1741132.html
new file mode 100644
index 0000000000..1ae9727d9c
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1741132.html
@@ -0,0 +1,79 @@
+
+
+
+  
+  Test form restoration for no-store pages
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1742865.html b/docshell/test/mochitest/test_bug1742865.html
new file mode 100644
index 0000000000..c8f9a4eca3
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1742865.html
@@ -0,0 +1,137 @@
+
+
+
+  
+  Auto refreshing pages shouldn't add an entry to session history
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1743353.html b/docshell/test/mochitest/test_bug1743353.html
new file mode 100644
index 0000000000..a5d88df3f6
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1743353.html
@@ -0,0 +1,57 @@
+
+
+
+  
+  Test back/forward after pushState
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1747033.html b/docshell/test/mochitest/test_bug1747033.html
new file mode 100644
index 0000000000..539b78fec0
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1747033.html
@@ -0,0 +1,97 @@
+
+
+
+  
+  Test history after loading multipart
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug1773192.html b/docshell/test/mochitest/test_bug1773192.html
new file mode 100644
index 0000000000..d4c42dc1a7
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1773192.html
@@ -0,0 +1,61 @@
+
+
+
+  
+  Test referrer with going back
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/mochitest/test_bug385434.html b/docshell/test/mochitest/test_bug385434.html
new file mode 100644
index 0000000000..bc82de9daf
--- /dev/null
+++ b/docshell/test/mochitest/test_bug385434.html
@@ -0,0 +1,211 @@
+
+
+
+
+  Test for Bug 385434
+  
+  
+  
+
+
+Mozilla Bug 385434
+

+
+ + +
+
+
+
+ + diff --git a/docshell/test/mochitest/test_bug387979.html b/docshell/test/mochitest/test_bug387979.html new file mode 100644 index 0000000000..0aca2ee89b --- /dev/null +++ b/docshell/test/mochitest/test_bug387979.html @@ -0,0 +1,52 @@ + + + + + Test for Bug 387979 + + + + +Mozilla Bug 387979 + +
+
+
+

+ +

-
+

+ + + diff --git a/docshell/test/mochitest/test_bug402210.html b/docshell/test/mochitest/test_bug402210.html new file mode 100644 index 0000000000..326f98cf9f --- /dev/null +++ b/docshell/test/mochitest/test_bug402210.html @@ -0,0 +1,50 @@ + + + + + Test for Bug 402210 + + + + +Mozilla Bug 402210 +

+ Test Link +

+ +
+
+
+ + + diff --git a/docshell/test/mochitest/test_bug404548.html b/docshell/test/mochitest/test_bug404548.html new file mode 100644 index 0000000000..a8a773dce5 --- /dev/null +++ b/docshell/test/mochitest/test_bug404548.html @@ -0,0 +1,39 @@ + + + + + Test for Bug 404548 + + + + +Mozilla Bug 404548 +

+

+ +
+
+
+ + + diff --git a/docshell/test/mochitest/test_bug413310.html b/docshell/test/mochitest/test_bug413310.html new file mode 100644 index 0000000000..4299605575 --- /dev/null +++ b/docshell/test/mochitest/test_bug413310.html @@ -0,0 +1,106 @@ + + + + + Test for Bug 413310 + + + + +Mozilla Bug 413310 +

+ + + +

+ +
+
+ + + diff --git a/docshell/test/mochitest/test_bug475636.html b/docshell/test/mochitest/test_bug475636.html new file mode 100644 index 0000000000..fb1827ad04 --- /dev/null +++ b/docshell/test/mochitest/test_bug475636.html @@ -0,0 +1,52 @@ + + + + + Test for Bug 475636 + + + + + +Mozilla Bug 475636 + + + +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug509055.html b/docshell/test/mochitest/test_bug509055.html new file mode 100644 index 0000000000..57ede19b43 --- /dev/null +++ b/docshell/test/mochitest/test_bug509055.html @@ -0,0 +1,115 @@ + + + + + Test for Bug 509055 + + + + + +Mozilla Bug 509055 +

+
+
+
+
+  
+
+
+
diff --git a/docshell/test/mochitest/test_bug511449.html b/docshell/test/mochitest/test_bug511449.html
new file mode 100644
index 0000000000..da95909d1c
--- /dev/null
+++ b/docshell/test/mochitest/test_bug511449.html
@@ -0,0 +1,56 @@
+
+
+
+
+  Test for Bug 511449
+  
+  
+  
+  
+
+
+Mozilla Bug 511449
+

+
+
+
+ +
+
+
+
+
diff --git a/docshell/test/mochitest/test_bug529119-1.html b/docshell/test/mochitest/test_bug529119-1.html
new file mode 100644
index 0000000000..1c89780fc7
--- /dev/null
+++ b/docshell/test/mochitest/test_bug529119-1.html
@@ -0,0 +1,110 @@
+
+
+
+Test bug 529119
+
+
+
+
+
+
+
+
diff --git a/docshell/test/mochitest/test_bug529119-2.html b/docshell/test/mochitest/test_bug529119-2.html
new file mode 100644
index 0000000000..a8bd57d4f7
--- /dev/null
+++ b/docshell/test/mochitest/test_bug529119-2.html
@@ -0,0 +1,116 @@
+
+
+
+Test bug 529119
+
+
+
+
+
+
+
+
diff --git a/docshell/test/mochitest/test_bug530396.html b/docshell/test/mochitest/test_bug530396.html
new file mode 100644
index 0000000000..fa3ddc6db6
--- /dev/null
+++ b/docshell/test/mochitest/test_bug530396.html
@@ -0,0 +1,56 @@
+
+
+
+
+  Test for Bug 530396
+  
+  
+  
+
+
+Mozilla Bug 530396
+
+

+ + + +

+
+
+ diff --git a/docshell/test/mochitest/test_bug540462.html b/docshell/test/mochitest/test_bug540462.html new file mode 100644 index 0000000000..e0a0861aaf --- /dev/null +++ b/docshell/test/mochitest/test_bug540462.html @@ -0,0 +1,44 @@ + + + + + Test for Bug 540462 + + + + +Mozilla Bug 540462 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug551225.html b/docshell/test/mochitest/test_bug551225.html new file mode 100644 index 0000000000..b2862fcad8 --- /dev/null +++ b/docshell/test/mochitest/test_bug551225.html @@ -0,0 +1,32 @@ + + + + + Test for Bug 551225 + + + + + +Mozilla Bug 551225 + + + + diff --git a/docshell/test/mochitest/test_bug570341.html b/docshell/test/mochitest/test_bug570341.html new file mode 100644 index 0000000000..363f985407 --- /dev/null +++ b/docshell/test/mochitest/test_bug570341.html @@ -0,0 +1,142 @@ + + + + + Test for Bug 570341 + + + + + +Mozilla Bug 570341 +
+ +
+ +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug580069.html b/docshell/test/mochitest/test_bug580069.html new file mode 100644 index 0000000000..bb0a3bc823 --- /dev/null +++ b/docshell/test/mochitest/test_bug580069.html @@ -0,0 +1,58 @@ + + + + + Test for Bug 580069 + + + + + +Mozilla Bug 580069 + + + + + diff --git a/docshell/test/mochitest/test_bug590573.html b/docshell/test/mochitest/test_bug590573.html new file mode 100644 index 0000000000..83554a7a66 --- /dev/null +++ b/docshell/test/mochitest/test_bug590573.html @@ -0,0 +1,198 @@ + + + + + Test for Bug 590573 + + + + + +Mozilla Bug 590573 + + + + + diff --git a/docshell/test/mochitest/test_bug598895.html b/docshell/test/mochitest/test_bug598895.html new file mode 100644 index 0000000000..e0b17e2663 --- /dev/null +++ b/docshell/test/mochitest/test_bug598895.html @@ -0,0 +1,52 @@ + + + + + Test for Bug 598895 + + + + + +Mozilla Bug 598895 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug634834.html b/docshell/test/mochitest/test_bug634834.html new file mode 100644 index 0000000000..e1f87de000 --- /dev/null +++ b/docshell/test/mochitest/test_bug634834.html @@ -0,0 +1,52 @@ + + + + + Test for Bug 634834 + + + + + +Mozilla Bug 634834 + + + + + + + diff --git a/docshell/test/mochitest/test_bug637644.html b/docshell/test/mochitest/test_bug637644.html new file mode 100644 index 0000000000..1e5f4380b4 --- /dev/null +++ b/docshell/test/mochitest/test_bug637644.html @@ -0,0 +1,52 @@ + + + + + Test for Bug 637644 + + + + + +Mozilla Bug 637644 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug640387_1.html b/docshell/test/mochitest/test_bug640387_1.html new file mode 100644 index 0000000000..b8aab054a1 --- /dev/null +++ b/docshell/test/mochitest/test_bug640387_1.html @@ -0,0 +1,107 @@ + + + + + Test for Bug 640387 + + + + + +Mozilla Bug 640387 + + + + + diff --git a/docshell/test/mochitest/test_bug640387_2.html b/docshell/test/mochitest/test_bug640387_2.html new file mode 100644 index 0000000000..c248a64836 --- /dev/null +++ b/docshell/test/mochitest/test_bug640387_2.html @@ -0,0 +1,89 @@ + + + + + Test for Bug 640387 + + + + + +Mozilla Bug 640387 + + + + + + + + + diff --git a/docshell/test/mochitest/test_bug653741.html b/docshell/test/mochitest/test_bug653741.html new file mode 100644 index 0000000000..33ba7077e4 --- /dev/null +++ b/docshell/test/mochitest/test_bug653741.html @@ -0,0 +1,49 @@ + + + + + Test for Bug 653741 + + + + + +Mozilla Bug 653741 + + + + + + + diff --git a/docshell/test/mochitest/test_bug660404.html b/docshell/test/mochitest/test_bug660404.html new file mode 100644 index 0000000000..94e3f67aa1 --- /dev/null +++ b/docshell/test/mochitest/test_bug660404.html @@ -0,0 +1,76 @@ + + + + + Test for Bug 660404 + + + + +Mozilla Bug 660404 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug662170.html b/docshell/test/mochitest/test_bug662170.html new file mode 100644 index 0000000000..d25ee3bd0f --- /dev/null +++ b/docshell/test/mochitest/test_bug662170.html @@ -0,0 +1,51 @@ + + + + + Test for Bug 662170 + + + + + +Mozilla Bug 662170 + + + + + + + + diff --git a/docshell/test/mochitest/test_bug668513.html b/docshell/test/mochitest/test_bug668513.html new file mode 100644 index 0000000000..09c848b6c1 --- /dev/null +++ b/docshell/test/mochitest/test_bug668513.html @@ -0,0 +1,28 @@ + + + + + Test for Bug 668513 + + + + +Mozilla Bug 668513 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug669671.html b/docshell/test/mochitest/test_bug669671.html new file mode 100644 index 0000000000..4470cd6682 --- /dev/null +++ b/docshell/test/mochitest/test_bug669671.html @@ -0,0 +1,145 @@ + + + + + Test for Bug 669671 + + + + +Mozilla Bug 669671 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug675587.html b/docshell/test/mochitest/test_bug675587.html new file mode 100644 index 0000000000..452bbc8058 --- /dev/null +++ b/docshell/test/mochitest/test_bug675587.html @@ -0,0 +1,33 @@ + + + + + Test for Bug 675587 + + + + +Mozilla Bug 675587 +

+ +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug680257.html b/docshell/test/mochitest/test_bug680257.html new file mode 100644 index 0000000000..4d5736ac0a --- /dev/null +++ b/docshell/test/mochitest/test_bug680257.html @@ -0,0 +1,76 @@ + + + + + Test for Bug 680257 + + + + +Mozilla Bug 680257 + + + + diff --git a/docshell/test/mochitest/test_bug691547.html b/docshell/test/mochitest/test_bug691547.html new file mode 100644 index 0000000000..706cd5013b --- /dev/null +++ b/docshell/test/mochitest/test_bug691547.html @@ -0,0 +1,59 @@ + + + + + Test for Bug 691547 + + + + + +Mozilla Bug 570341 +
+ +
+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug694612.html b/docshell/test/mochitest/test_bug694612.html new file mode 100644 index 0000000000..8ea4331c43 --- /dev/null +++ b/docshell/test/mochitest/test_bug694612.html @@ -0,0 +1,34 @@ + + + + + Test for Bug 694612 + + + + +Mozilla Bug 694612 +

+ +
+
+
+
+
+ + diff --git a/docshell/test/mochitest/test_bug703855.html b/docshell/test/mochitest/test_bug703855.html new file mode 100644 index 0000000000..4aa7b08800 --- /dev/null +++ b/docshell/test/mochitest/test_bug703855.html @@ -0,0 +1,79 @@ + + + + + + Test for Bug 703855 + + + + +Mozilla Bug 703855 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_bug728939.html b/docshell/test/mochitest/test_bug728939.html new file mode 100644 index 0000000000..168184099a --- /dev/null +++ b/docshell/test/mochitest/test_bug728939.html @@ -0,0 +1,37 @@ + + + + + Test for Bug 728939 + + + + +Mozilla Bug 728939 + + + + diff --git a/docshell/test/mochitest/test_bug797909.html b/docshell/test/mochitest/test_bug797909.html new file mode 100644 index 0000000000..15b7c27c0f --- /dev/null +++ b/docshell/test/mochitest/test_bug797909.html @@ -0,0 +1,66 @@ + + + + + + Test for Bug 797909 + + + + +Mozilla Bug 797909 +

+ +
+
+
+ + + diff --git a/docshell/test/mochitest/test_close_onpagehide_by_history_back.html b/docshell/test/mochitest/test_close_onpagehide_by_history_back.html new file mode 100644 index 0000000000..33140502f7 --- /dev/null +++ b/docshell/test/mochitest/test_close_onpagehide_by_history_back.html @@ -0,0 +1,24 @@ + +Test for closing window in pagehide event callback caused by history.back() + + +Mozilla Bug 1432396 +

+ diff --git a/docshell/test/mochitest/test_close_onpagehide_by_window_close.html b/docshell/test/mochitest/test_close_onpagehide_by_window_close.html new file mode 100644 index 0000000000..8b094cdaa4 --- /dev/null +++ b/docshell/test/mochitest/test_close_onpagehide_by_window_close.html @@ -0,0 +1,20 @@ + +Test for closing window in pagehide event callback caused by window.close() + + +Mozilla Bug 1432396 +

+ diff --git a/docshell/test/mochitest/test_compressed_multipart.html b/docshell/test/mochitest/test_compressed_multipart.html new file mode 100644 index 0000000000..438819b643 --- /dev/null +++ b/docshell/test/mochitest/test_compressed_multipart.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 1600211 + + + + +Mozilla Bug 1600211 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/test_content_javascript_loads.html b/docshell/test/mochitest/test_content_javascript_loads.html new file mode 100644 index 0000000000..eabc1d314e --- /dev/null +++ b/docshell/test/mochitest/test_content_javascript_loads.html @@ -0,0 +1,163 @@ + + + + Test for Bug 1647519 + + + + + + +Mozilla Bug 1647519 + + + + + diff --git a/docshell/test/mochitest/test_double_submit.html b/docshell/test/mochitest/test_double_submit.html new file mode 100644 index 0000000000..640930718d --- /dev/null +++ b/docshell/test/mochitest/test_double_submit.html @@ -0,0 +1,98 @@ + + + + Test for Bug 1590762 + + + + + + +
+ + +
+ + + diff --git a/docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html b/docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html new file mode 100644 index 0000000000..70d610a677 --- /dev/null +++ b/docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html @@ -0,0 +1,57 @@ + + + + + + + + + +
+ + diff --git a/docshell/test/mochitest/test_form_restoration.html b/docshell/test/mochitest/test_form_restoration.html new file mode 100644 index 0000000000..b929236770 --- /dev/null +++ b/docshell/test/mochitest/test_form_restoration.html @@ -0,0 +1,77 @@ + + + + + Test form restoration for no-store pages + + + + + +

+ +

+
+
diff --git a/docshell/test/mochitest/test_framedhistoryframes.html b/docshell/test/mochitest/test_framedhistoryframes.html
new file mode 100644
index 0000000000..f90028af0a
--- /dev/null
+++ b/docshell/test/mochitest/test_framedhistoryframes.html
@@ -0,0 +1,32 @@
+
+
+
+
+  Test for Bug 602256
+  
+  
+
+
+Mozilla Bug 602256
+

+
+
+
+
+
+ + diff --git a/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html b/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html new file mode 100644 index 0000000000..05e0934d50 --- /dev/null +++ b/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/docshell/test/mochitest/test_javascript_sandboxed_popup.html b/docshell/test/mochitest/test_javascript_sandboxed_popup.html new file mode 100644 index 0000000000..edce93c26f --- /dev/null +++ b/docshell/test/mochitest/test_javascript_sandboxed_popup.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/docshell/test/mochitest/test_load_during_reload.html b/docshell/test/mochitest/test_load_during_reload.html new file mode 100644 index 0000000000..88856b0100 --- /dev/null +++ b/docshell/test/mochitest/test_load_during_reload.html @@ -0,0 +1,49 @@ + + + + + Test loading a new page after calling reload() + + + + + +

+ +

+
+
diff --git a/docshell/test/mochitest/test_navigate_after_pagehide.html b/docshell/test/mochitest/test_navigate_after_pagehide.html
new file mode 100644
index 0000000000..17d58d6e62
--- /dev/null
+++ b/docshell/test/mochitest/test_navigate_after_pagehide.html
@@ -0,0 +1,34 @@
+
+
+  
+    Test for navigation attempts by scripts in inactive inner window
+    
+    
+  
+
+
+
+
+
+
diff --git a/docshell/test/mochitest/test_pushState_after_document_open.html b/docshell/test/mochitest/test_pushState_after_document_open.html
new file mode 100644
index 0000000000..b81951b7ae
--- /dev/null
+++ b/docshell/test/mochitest/test_pushState_after_document_open.html
@@ -0,0 +1,39 @@
+
+
+
+
+  
+  Test for Bug 957479
+  
+  
+  
+
+
+Mozilla Bug 957479
+

+ +
+
+ + diff --git a/docshell/test/mochitest/test_redirect_history.html b/docshell/test/mochitest/test_redirect_history.html new file mode 100644 index 0000000000..a67c808405 --- /dev/null +++ b/docshell/test/mochitest/test_redirect_history.html @@ -0,0 +1,58 @@ + + + + Test for redirect from POST + + + + + + + + diff --git a/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html b/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html new file mode 100644 index 0000000000..92e20b03ea --- /dev/null +++ b/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html @@ -0,0 +1,105 @@ + + + + + + + + + + + diff --git a/docshell/test/mochitest/test_windowedhistoryframes.html b/docshell/test/mochitest/test_windowedhistoryframes.html new file mode 100644 index 0000000000..a874da098c --- /dev/null +++ b/docshell/test/mochitest/test_windowedhistoryframes.html @@ -0,0 +1,32 @@ + + + + + Test for Bug 602256 + + + + +Mozilla Bug 602256 +

+ +
+
+
+ + diff --git a/docshell/test/mochitest/url1_historyframe.html b/docshell/test/mochitest/url1_historyframe.html new file mode 100644 index 0000000000..b86af4b3fa --- /dev/null +++ b/docshell/test/mochitest/url1_historyframe.html @@ -0,0 +1 @@ +

Test1

diff --git a/docshell/test/mochitest/url2_historyframe.html b/docshell/test/mochitest/url2_historyframe.html new file mode 100644 index 0000000000..24374d1a5b --- /dev/null +++ b/docshell/test/mochitest/url2_historyframe.html @@ -0,0 +1 @@ +

Test2

diff --git a/docshell/test/moz.build b/docshell/test/moz.build new file mode 100644 index 0000000000..7cebe0339f --- /dev/null +++ b/docshell/test/moz.build @@ -0,0 +1,137 @@ +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Navigation") + +with Files("browser/*_bug234628*"): + BUG_COMPONENT = ("Core", "Internationalization") + +with Files("browser/*_bug349769*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("browser/*_bug388121*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("browser/*_bug655270*"): + BUG_COMPONENT = ("Toolkit", "Places") + +with Files("browser/*_bug655273*"): + BUG_COMPONENT = ("Firefox", "Menus") + +with Files("browser/*_bug852909*"): + BUG_COMPONENT = ("Firefox", "Menus") + +with Files("browser/*bug92473*"): + BUG_COMPONENT = ("Core", "Internationalization") + +with Files("browser/*loadDisallowInherit*"): + BUG_COMPONENT = ("Firefox", "Address Bar") + +with Files("browser/*tab_touch_events*"): + BUG_COMPONENT = ("Core", "DOM: Events") + +with Files("browser/*timelineMarkers*"): + BUG_COMPONENT = ("DevTools", "Performance Tools (Profiler/Timeline)") + +with Files("browser/*ua_emulation*"): + BUG_COMPONENT = ("DevTools", "General") + +with Files("chrome/*112564*"): + BUG_COMPONENT = ("Core", "Networking: HTTP") + +with Files("chrome/*303267*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("chrome/*453650*"): + BUG_COMPONENT = ("Core", "Layout") + +with Files("chrome/*565388*"): + BUG_COMPONENT = ("Core", "Widget") + +with Files("chrome/*582176*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("chrome/*608669*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("chrome/*690056*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("chrome/*92598*"): + BUG_COMPONENT = ("Core", "Networking: HTTP") + +with Files("iframesandbox/**"): + BUG_COMPONENT = ("Core", "Security") + +with Files("iframesandbox/*marquee_event_handlers*"): + BUG_COMPONENT = ("Core", "DOM: Security") + + +with Files("mochitest/*1045096*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*1151421*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*402210*"): + BUG_COMPONENT = ("Core", "DOM: Security") + +with Files("mochitest/*509055*"): + BUG_COMPONENT = ("Firefox", "Bookmarks & History") + +with Files("mochitest/*511449*"): + BUG_COMPONENT = ("Core", "Widget: Cocoa") + +with Files("mochitest/*551225*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*570341*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*580069*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*637644*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*640387*"): + BUG_COMPONENT = ("Core", "DOM: Events") + +with Files("mochitest/*668513*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*797909*"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +with Files("mochitest/*forceinheritprincipal*"): + BUG_COMPONENT = ("Core", "DOM: Security") + + +with Files("navigation/*13871.html"): + BUG_COMPONENT = ("Core", "Security") + +with Files("navigation/*386782*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("navigation/*430624*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("navigation/*430723*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +with Files("navigation/*child*"): + BUG_COMPONENT = ("Core", "Security") + +with Files("navigation/*opener*"): + BUG_COMPONENT = ("Core", "Security") + +with Files("navigation/*reserved*"): + BUG_COMPONENT = ("Core", "Security") + +with Files("navigation/*triggering*"): + BUG_COMPONENT = ("Core", "DOM: Security") + + +with Files("unit/*442584*"): + BUG_COMPONENT = ("Core", "Networking: Cache") + +with Files("unit/*setUsePrivateBrowsing*"): + BUG_COMPONENT = ("Firefox", "Extension Compatibility") diff --git a/docshell/test/navigation/NavigationUtils.js b/docshell/test/navigation/NavigationUtils.js new file mode 100644 index 0000000000..c4b52dc62f --- /dev/null +++ b/docshell/test/navigation/NavigationUtils.js @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// ///////////////////////////////////////////////////////////////////////// +// +// Utilities for navigation tests +// +// ///////////////////////////////////////////////////////////////////////// + +var body = "This frame was navigated."; +var target_url = "navigation_target_url.html"; + +var popup_body = "This is a popup"; +var target_popup_url = "navigation_target_popup_url.html"; + +// ///////////////////////////////////////////////////////////////////////// +// Functions that navigate frames +// ///////////////////////////////////////////////////////////////////////// + +function navigateByLocation(wnd) { + try { + wnd.location = target_url; + } catch (ex) { + // We need to keep our finished frames count consistent. + // Oddly, this ends up simulating the behavior of IE7. + window.open(target_url, "_blank", "width=10,height=10"); + } +} + +function navigateByOpen(name) { + window.open(target_url, name, "width=10,height=10"); +} + +function navigateByForm(name) { + var form = document.createElement("form"); + form.action = target_url; + form.method = "POST"; + form.target = name; + document.body.appendChild(form); + form.submit(); +} + +var hyperlink_count = 0; + +function navigateByHyperlink(name) { + var link = document.createElement("a"); + link.href = target_url; + link.target = name; + link.id = "navigation_hyperlink_" + hyperlink_count++; + document.body.appendChild(link); + sendMouseEvent({ type: "click" }, link.id); +} + +// ///////////////////////////////////////////////////////////////////////// +// Functions that call into Mochitest framework +// ///////////////////////////////////////////////////////////////////////// + +async function isNavigated(wnd, message) { + var result = null; + try { + result = await SpecialPowers.spawn(wnd, [], () => + this.content.document.body.innerHTML.trim() + ); + } catch (ex) { + result = ex; + } + is(result, body, message); +} + +function isBlank(wnd, message) { + var result = null; + try { + result = wnd.document.body.innerHTML.trim(); + } catch (ex) { + result = ex; + } + is(result, "This is a blank document.", message); +} + +function isAccessible(wnd, message) { + try { + wnd.document.body.innerHTML; + ok(true, message); + } catch (ex) { + ok(false, message); + } +} + +function isInaccessible(wnd, message) { + try { + wnd.document.body.innerHTML; + ok(false, message); + } catch (ex) { + ok(true, message); + } +} + +function delay(msec) { + return new Promise(resolve => setTimeout(resolve, msec)); +} + +// ///////////////////////////////////////////////////////////////////////// +// Functions that uses SpecialPowers.spawn +// ///////////////////////////////////////////////////////////////////////// + +async function waitForFinishedFrames(numFrames) { + SimpleTest.requestFlakyTimeout("Polling"); + + var finishedWindows = new Set(); + + async function searchForFinishedFrames(win) { + try { + let { href, bodyText, readyState } = await SpecialPowers.spawn( + win, + [], + () => { + return { + href: this.content.location.href, + bodyText: + this.content.document.body && + this.content.document.body.textContent.trim(), + readyState: this.content.document.readyState, + }; + } + ); + + if ( + (href.endsWith(target_url) || href.endsWith(target_popup_url)) && + (bodyText == body || bodyText == popup_body) && + readyState == "complete" + ) { + finishedWindows.add(SpecialPowers.getBrowsingContextID(win)); + } + } catch (e) { + // This may throw if a frame is not fully initialized, in which + // case we'll handle it in a later iteration. + } + + for (let i = 0; i < win.frames.length; i++) { + await searchForFinishedFrames(win.frames[i]); + } + } + + while (finishedWindows.size < numFrames) { + await delay(500); + + for (let win of SpecialPowers.getGroupTopLevelWindows(window)) { + win = SpecialPowers.unwrap(win); + await searchForFinishedFrames(win); + } + } + + if (finishedWindows.size > numFrames) { + throw new Error("Too many frames loaded."); + } +} + +async function getFramesByName(name) { + let results = []; + for (let win of SpecialPowers.getGroupTopLevelWindows(window)) { + win = SpecialPowers.unwrap(win); + if ( + (await SpecialPowers.spawn(win, [], () => this.content.name)) === name + ) { + results.push(win); + } + } + + return results; +} + +async function cleanupWindows() { + for (let win of SpecialPowers.getGroupTopLevelWindows(window)) { + win = SpecialPowers.unwrap(win); + if (win.closed) { + continue; + } + + let href = ""; + try { + href = await SpecialPowers.spawn( + win, + [], + () => + this.content && this.content.location && this.content.location.href + ); + } catch (error) { + // SpecialPowers.spawn(win, ...) throws if win is closed. We did + // our best to not call it on a closed window, but races happen. + if (!win.closed) { + throw error; + } + } + + if ( + href && + (href.endsWith(target_url) || href.endsWith(target_popup_url)) + ) { + win.close(); + } + } +} diff --git a/docshell/test/navigation/blank.html b/docshell/test/navigation/blank.html new file mode 100644 index 0000000000..5360333f1d --- /dev/null +++ b/docshell/test/navigation/blank.html @@ -0,0 +1 @@ +This is a blank document. \ No newline at end of file diff --git a/docshell/test/navigation/bluebox_bug430723.html b/docshell/test/navigation/bluebox_bug430723.html new file mode 100644 index 0000000000..5dcc533562 --- /dev/null +++ b/docshell/test/navigation/bluebox_bug430723.html @@ -0,0 +1,6 @@ + + + +
+

This is a very tall blue box.

+
diff --git a/docshell/test/navigation/browser.ini b/docshell/test/navigation/browser.ini new file mode 100644 index 0000000000..baca6b401c --- /dev/null +++ b/docshell/test/navigation/browser.ini @@ -0,0 +1,23 @@ +[DEFAULT] +support-files = + bug343515_pg1.html + bug343515_pg2.html + bug343515_pg3.html + bug343515_pg3_1.html + bug343515_pg3_1_1.html + bug343515_pg3_2.html + blank.html + redirect_to_blank.sjs + +[browser_bug1757458.js] +[browser_test_bfcache_eviction.js] +[browser_bug343515.js] +[browser_test-content-chromeflags.js] +tags = openwindow +[browser_ghistorymaxsize_is_0.js] +[browser_test_shentry_wireframe.js] +skip-if = + !sessionHistoryInParent + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_test_simultaneous_normal_and_history_loads.js] +skip-if = !sessionHistoryInParent # The test is for the new session history diff --git a/docshell/test/navigation/browser_bug1757458.js b/docshell/test/navigation/browser_bug1757458.js new file mode 100644 index 0000000000..cc9504ac1c --- /dev/null +++ b/docshell/test/navigation/browser_bug1757458.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function () { + let testPage = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "view-source:http://example.com" + ) + "redirect_to_blank.sjs"; + + let testPage2 = "data:text/html,
Second page
"; + await BrowserTestUtils.withNewTab( + { gBrowser, url: testPage }, + async function (browser) { + await ContentTask.spawn(browser, [], async () => { + Assert.ok( + content.document.getElementById("viewsource").localName == "body", + "view-source document's body should have id='viewsource'." + ); + content.document + .getElementById("viewsource") + .setAttribute("onunload", "/* disable bfcache*/"); + }); + + BrowserTestUtils.loadURIString(browser, testPage2); + await BrowserTestUtils.browserLoaded(browser); + + let pageShownPromise = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow", + true + ); + browser.browsingContext.goBack(); + await pageShownPromise; + + await ContentTask.spawn(browser, [], async () => { + Assert.ok( + content.document.getElementById("viewsource").localName == "body", + "view-source document's body should have id='viewsource'." + ); + }); + } + ); +}); diff --git a/docshell/test/navigation/browser_bug343515.js b/docshell/test/navigation/browser_bug343515.js new file mode 100644 index 0000000000..722b1bc018 --- /dev/null +++ b/docshell/test/navigation/browser_bug343515.js @@ -0,0 +1,276 @@ +// Test for bug 343515 - Need API for tabbrowsers to tell docshells they're visible/hidden + +// Globals +var testPath = "http://mochi.test:8888/browser/docshell/test/navigation/"; +var ctx = {}; + +add_task(async function () { + // Step 1. + + // Get a handle on the initial tab + ctx.tab0 = gBrowser.selectedTab; + ctx.tab0Browser = gBrowser.getBrowserForTab(ctx.tab0); + + await BrowserTestUtils.waitForCondition( + () => ctx.tab0Browser.docShellIsActive, + "Timed out waiting for initial tab to be active." + ); + + // Open a New Tab + ctx.tab1 = BrowserTestUtils.addTab(gBrowser, testPath + "bug343515_pg1.html"); + ctx.tab1Browser = gBrowser.getBrowserForTab(ctx.tab1); + await BrowserTestUtils.browserLoaded(ctx.tab1Browser); + + // Step 2. + is( + testPath + "bug343515_pg1.html", + ctx.tab1Browser.currentURI.spec, + "Got expected tab 1 url in step 2" + ); + + // Our current tab should still be active + ok(ctx.tab0Browser.docShellIsActive, "Tab 0 should still be active"); + ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should not be active"); + + // Switch to tab 1 + await BrowserTestUtils.switchTab(gBrowser, ctx.tab1); + + // Tab 1 should now be active + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + + // Open another tab + ctx.tab2 = BrowserTestUtils.addTab(gBrowser, testPath + "bug343515_pg2.html"); + ctx.tab2Browser = gBrowser.getBrowserForTab(ctx.tab2); + + await BrowserTestUtils.browserLoaded(ctx.tab2Browser); + + // Step 3. + is( + testPath + "bug343515_pg2.html", + ctx.tab2Browser.currentURI.spec, + "Got expected tab 2 url in step 3" + ); + + // Tab 0 should be inactive, Tab 1 should be active + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + + // Tab 2's window _and_ its iframes should be inactive + ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive"); + + await SpecialPowers.spawn(ctx.tab2Browser, [], async function () { + Assert.equal(content.frames.length, 2, "Tab 2 should have 2 iframes"); + for (var i = 0; i < content.frames.length; i++) { + info("step 3, frame " + i + " info: " + content.frames[i].location); + let bc = content.frames[i].browsingContext; + Assert.ok(!bc.isActive, `Tab2 iframe ${i} should be inactive`); + } + }); + + // Navigate tab 2 to a different page + BrowserTestUtils.loadURIString( + ctx.tab2Browser, + testPath + "bug343515_pg3.html" + ); + + await BrowserTestUtils.browserLoaded(ctx.tab2Browser); + + // Step 4. + + async function checkTab2Active(outerExpected) { + await SpecialPowers.spawn( + ctx.tab2Browser, + [outerExpected], + async function (expected) { + function isActive(aWindow) { + var docshell = aWindow.docShell; + info(`checking ${docshell.browsingContext.id}`); + return docshell.browsingContext.isActive; + } + + let active = expected ? "active" : "inactive"; + Assert.equal(content.frames.length, 2, "Tab 2 should have 2 iframes"); + for (var i = 0; i < content.frames.length; i++) { + info("step 4, frame " + i + " info: " + content.frames[i].location); + } + Assert.equal( + content.frames[0].frames.length, + 1, + "Tab 2 iframe 0 should have 1 iframes" + ); + Assert.equal( + isActive(content.frames[0]), + expected, + `Tab2 iframe 0 should be ${active}` + ); + Assert.equal( + isActive(content.frames[0].frames[0]), + expected, + `Tab2 iframe 0 subiframe 0 should be ${active}` + ); + Assert.equal( + isActive(content.frames[1]), + expected, + `Tab2 iframe 1 should be ${active}` + ); + } + ); + } + + is( + testPath + "bug343515_pg3.html", + ctx.tab2Browser.currentURI.spec, + "Got expected tab 2 url in step 4" + ); + + // Tab 0 should be inactive, Tab 1 should be active + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + + // Tab2 and all descendants should be inactive + await checkTab2Active(false); + + // Switch to Tab 2 + await BrowserTestUtils.switchTab(gBrowser, ctx.tab2); + + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should be inactive"); + ok(ctx.tab2Browser.docShellIsActive, "Tab 2 should be active"); + + await checkTab2Active(true); + + // Go back + let backDone = BrowserTestUtils.waitForLocationChange( + gBrowser, + testPath + "bug343515_pg2.html" + ); + ctx.tab2Browser.goBack(); + await backDone; + + // Step 5. + + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should be inactive"); + ok(ctx.tab2Browser.docShellIsActive, "Tab 2 should be active"); + is( + testPath + "bug343515_pg2.html", + ctx.tab2Browser.currentURI.spec, + "Got expected tab 2 url in step 5" + ); + + await SpecialPowers.spawn(ctx.tab2Browser, [], async function () { + for (var i = 0; i < content.frames.length; i++) { + let bc = content.frames[i].browsingContext; + Assert.ok(bc.isActive, `Tab2 iframe ${i} should be active`); + } + }); + + // Switch to tab 1 + await BrowserTestUtils.switchTab(gBrowser, ctx.tab1); + + // Navigate to page 3 + BrowserTestUtils.loadURIString( + ctx.tab1Browser, + testPath + "bug343515_pg3.html" + ); + + await BrowserTestUtils.browserLoaded(ctx.tab1Browser); + + // Step 6. + + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + is( + testPath + "bug343515_pg3.html", + ctx.tab1Browser.currentURI.spec, + "Got expected tab 1 url in step 6" + ); + + await SpecialPowers.spawn(ctx.tab1Browser, [], async function () { + function isActive(aWindow) { + var docshell = aWindow.docShell; + info(`checking ${docshell.browsingContext.id}`); + return docshell.browsingContext.isActive; + } + + Assert.ok(isActive(content.frames[0]), "Tab1 iframe 0 should be active"); + Assert.ok( + isActive(content.frames[0].frames[0]), + "Tab1 iframe 0 subiframe 0 should be active" + ); + Assert.ok(isActive(content.frames[1]), "Tab1 iframe 1 should be active"); + }); + + ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive"); + + await SpecialPowers.spawn(ctx.tab2Browser, [], async function () { + for (var i = 0; i < content.frames.length; i++) { + let bc = content.frames[i].browsingContext; + Assert.ok(!bc.isActive, `Tab2 iframe ${i} should be inactive`); + } + }); + + // Go forward on tab 2 + let forwardDone = BrowserTestUtils.waitForLocationChange( + gBrowser, + testPath + "bug343515_pg3.html" + ); + ctx.tab2Browser.goForward(); + await forwardDone; + + // Step 7. + + async function checkBrowser(browser, outerTabNum, outerActive) { + let data = { tabNum: outerTabNum, active: outerActive }; + await SpecialPowers.spawn( + browser, + [data], + async function ({ tabNum, active }) { + function isActive(aWindow) { + var docshell = aWindow.docShell; + info(`checking ${docshell.browsingContext.id}`); + return docshell.browsingContext.isActive; + } + + let activestr = active ? "active" : "inactive"; + Assert.equal( + isActive(content.frames[0]), + active, + `Tab${tabNum} iframe 0 should be ${activestr}` + ); + Assert.equal( + isActive(content.frames[0].frames[0]), + active, + `Tab${tabNum} iframe 0 subiframe 0 should be ${activestr}` + ); + Assert.equal( + isActive(content.frames[1]), + active, + `Tab${tabNum} iframe 1 should be ${activestr}` + ); + } + ); + } + + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + is( + testPath + "bug343515_pg3.html", + ctx.tab2Browser.currentURI.spec, + "Got expected tab 2 url in step 7" + ); + + await checkBrowser(ctx.tab1Browser, 1, true); + + ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive"); + await checkBrowser(ctx.tab2Browser, 2, false); + + // Close the tabs we made + BrowserTestUtils.removeTab(ctx.tab1); + BrowserTestUtils.removeTab(ctx.tab2); +}); diff --git a/docshell/test/navigation/browser_ghistorymaxsize_is_0.js b/docshell/test/navigation/browser_ghistorymaxsize_is_0.js new file mode 100644 index 0000000000..43c36bbaf2 --- /dev/null +++ b/docshell/test/navigation/browser_ghistorymaxsize_is_0.js @@ -0,0 +1,82 @@ +add_task(async function () { + // The urls don't really matter as long as they are of the same origin + var URL = + "http://mochi.test:8888/browser/docshell/test/navigation/bug343515_pg1.html"; + var URL2 = + "http://mochi.test:8888/browser/docshell/test/navigation/bug343515_pg3_1.html"; + + // We want to test a specific code path that leads to this call + // https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/docshell/base/nsDocShell.cpp#10795 + // when gHistoryMaxSize is 0 and mIndex and mRequestedIndex are -1 + + // 1. Navigate to URL + await BrowserTestUtils.withNewTab( + { gBrowser, url: URL }, + async function (browser) { + // At this point, we haven't set gHistoryMaxSize to 0, and it is still 50 (default value). + if (SpecialPowers.Services.appinfo.sessionHistoryInParent) { + let sh = browser.browsingContext.sessionHistory; + is( + sh.count, + 1, + "We should have entry in session history because we haven't changed gHistoryMaxSize to be 0 yet" + ); + is( + sh.index, + 0, + "Shistory's current index should be 0 because we haven't purged history yet" + ); + } else { + await ContentTask.spawn(browser, null, () => { + var sh = content.window.docShell.QueryInterface(Ci.nsIWebNavigation) + .sessionHistory.legacySHistory; + is( + sh.count, + 1, + "We should have entry in session history because we haven't changed gHistoryMaxSize to be 0 yet" + ); + is( + sh.index, + 0, + "Shistory's current index should be 0 because we haven't purged history yet" + ); + }); + } + + var loadPromise = BrowserTestUtils.browserLoaded(browser, false, URL2); + // If we set the pref at the beginning of this page, then when we launch a child process + // to navigate to URL in Step 1, because of + // https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/docshell/shistory/nsSHistory.cpp#308-312 + // this pref will be set to the default value (currently 50). Setting this pref after the child process launches + // is a robust way to make sure it stays 0 + await SpecialPowers.pushPrefEnv({ + set: [["browser.sessionhistory.max_entries", 0]], + }); + // 2. Navigate to URL2 + // We are navigating to a page with the same origin so that we will stay in the same process + BrowserTestUtils.loadURIString(browser, URL2); + await loadPromise; + + // 3. Reload the browser with specific flags so that we end up here + // https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/docshell/base/nsDocShell.cpp#10795 + var promise = BrowserTestUtils.browserLoaded(browser); + browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); + await promise; + + if (SpecialPowers.Services.appinfo.sessionHistoryInParent) { + let sh = browser.browsingContext.sessionHistory; + is(sh.count, 0, "We should not save any entries in session history"); + is(sh.index, -1); + is(sh.requestedIndex, -1); + } else { + await ContentTask.spawn(browser, null, () => { + var sh = content.window.docShell.QueryInterface(Ci.nsIWebNavigation) + .sessionHistory.legacySHistory; + is(sh.count, 0, "We should not save any entries in session history"); + is(sh.index, -1); + is(sh.requestedIndex, -1); + }); + } + } + ); +}); diff --git a/docshell/test/navigation/browser_test-content-chromeflags.js b/docshell/test/navigation/browser_test-content-chromeflags.js new file mode 100644 index 0000000000..ab1ae41c0c --- /dev/null +++ b/docshell/test/navigation/browser_test-content-chromeflags.js @@ -0,0 +1,54 @@ +const TEST_PAGE = `data:text/html,Test`; +const { CHROME_ALL, CHROME_REMOTE_WINDOW, CHROME_FISSION_WINDOW } = + Ci.nsIWebBrowserChrome; + +/** + * Tests that when we open new browser windows from content they + * get the full browser chrome. + */ +add_task(async function () { + // Make sure that the window.open call will open a new + // window instead of a new tab. + await new Promise(resolve => { + SpecialPowers.pushPrefEnv( + { + set: [["browser.link.open_newwindow", 2]], + }, + resolve + ); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async function (browser) { + let openedPromise = BrowserTestUtils.waitForNewWindow(); + BrowserTestUtils.synthesizeMouse("a", 0, 0, {}, browser); + let win = await openedPromise; + + let chromeFlags = win.docShell.treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIAppWindow).chromeFlags; + + let expected = CHROME_ALL; + + // In the multi-process tab case, the new window will have the + // CHROME_REMOTE_WINDOW flag set. + if (gMultiProcessBrowser) { + expected |= CHROME_REMOTE_WINDOW; + } + + // In the multi-process subframe case, the new window will have the + // CHROME_FISSION_WINDOW flag set. + if (gFissionBrowser) { + expected |= CHROME_FISSION_WINDOW; + } + + is(chromeFlags, expected, "Window should have opened with all chrome"); + + await BrowserTestUtils.closeWindow(win); + } + ); +}); diff --git a/docshell/test/navigation/browser_test_bfcache_eviction.js b/docshell/test/navigation/browser_test_bfcache_eviction.js new file mode 100644 index 0000000000..32772fe42c --- /dev/null +++ b/docshell/test/navigation/browser_test_bfcache_eviction.js @@ -0,0 +1,102 @@ +add_task(async function () { + // We don't want the number of total viewers to be calculated by the available size + // for this test case. Instead, fix the number of viewers. + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.sessionhistory.max_total_viewers", 3], + ["docshell.shistory.testing.bfevict", true], + // If Fission is disabled, the pref is no-op. + ["fission.bfcacheInParent", true], + ], + }); + + // 1. Open a tab + var testPage = + "data:text/html,First tab ever opened"; + await BrowserTestUtils.withNewTab( + { gBrowser, url: testPage }, + async function (browser) { + let testDone = {}; + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + // 2. Add a promise that will be resolved when the 'content viewer evicted' event goes off + testDone.promise = SpecialPowers.spawn(browser, [], async function () { + return new Promise(resolve => { + let webNavigation = content.docShell.QueryInterface( + Ci.nsIWebNavigation + ); + let { legacySHistory } = webNavigation.sessionHistory; + // 3. Register a session history listener to listen for a 'content viewer evicted' event. + let historyListener = { + OnContentViewerEvicted() { + ok( + true, + "History listener got called after a content viewer was evicted" + ); + legacySHistory.removeSHistoryListener(historyListener); + // 6. Resolve the promise when we got our 'content viewer evicted' event + resolve(); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsISHistoryListener", + "nsISupportsWeakReference", + ]), + }; + legacySHistory.addSHistoryListener(historyListener); + // Keep the weak shistory listener alive + content._testListener = historyListener; + }); + }); + } else { + // 2. Add a promise that will be resolved when the 'content viewer evicted' event goes off + testDone.promise = new Promise(resolve => { + testDone.resolve = resolve; + }); + let shistory = browser.browsingContext.sessionHistory; + // 3. Register a session history listener to listen for a 'content viewer evicted' event. + let historyListener = { + OnContentViewerEvicted() { + ok( + true, + "History listener got called after a content viewer was evicted" + ); + shistory.removeSHistoryListener(historyListener); + delete window._testListener; + // 6. Resolve the promise when we got our 'content viewer evicted' event + testDone.resolve(); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsISHistoryListener", + "nsISupportsWeakReference", + ]), + }; + shistory.addSHistoryListener(historyListener); + // Keep the weak shistory listener alive + window._testListener = historyListener; + } + + // 4. Open a second tab + testPage = `data:text/html,I am a second tab!`; + let tab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + testPage + ); + + // 5. Navigate the first tab to 4 different pages. + // We should get 1 content viewer evicted because it will be outside of the range. + // If we have the following pages in our session history: P1 P2 P3 P4 P5 + // and we are currently at P5, then P1 is outside of the range + // (it is more than 3 entries away from current entry) and thus will be evicted. + for (var i = 0; i < 4; i++) { + testPage = `data:text/html,${i}`; + let pagePromise = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.loadURIString(browser, testPage); + await pagePromise; + } + // 7. Wait for 'content viewer evicted' event to go off + await testDone.promise; + + // 8. Close the second tab + BrowserTestUtils.removeTab(tab2); + } + ); +}); diff --git a/docshell/test/navigation/browser_test_shentry_wireframe.js b/docshell/test/navigation/browser_test_shentry_wireframe.js new file mode 100644 index 0000000000..953a2eb6e2 --- /dev/null +++ b/docshell/test/navigation/browser_test_shentry_wireframe.js @@ -0,0 +1,128 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BUILDER = "http://mochi.test:8888/document-builder.sjs?html="; +const PAGE_1 = BUILDER + encodeURIComponent(`Page 1`); +const PAGE_2 = BUILDER + encodeURIComponent(`Page 2`); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["browser.history.collectWireframes", true]], + }); +}); + +/** + * Test that capturing wireframes on nsISHEntriy's in the parent process + * happens at the right times. + */ +add_task(async function () { + await BrowserTestUtils.withNewTab(PAGE_1, async browser => { + let sh = browser.browsingContext.sessionHistory; + Assert.equal( + sh.count, + 1, + "Got the right SessionHistory entry count after initial tab load." + ); + Assert.ok( + !sh.getEntryAtIndex(0).wireframe, + "No wireframe for the loaded entry after initial tab load." + ); + + let loaded = BrowserTestUtils.browserLoaded(browser, false, PAGE_2); + BrowserTestUtils.loadURIString(browser, PAGE_2); + await loaded; + + Assert.equal( + sh.count, + 2, + "Got the right SessionHistory entry count after loading page 2." + ); + Assert.ok( + sh.getEntryAtIndex(0).wireframe, + "A wireframe was captured for the first entry after loading page 2." + ); + Assert.ok( + !sh.getEntryAtIndex(1).wireframe, + "No wireframe for the loaded entry after loading page 2." + ); + + // Now go back + loaded = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); + browser.goBack(); + await loaded; + Assert.ok( + sh.getEntryAtIndex(1).wireframe, + "A wireframe was captured for the second entry after going back." + ); + Assert.ok( + !sh.getEntryAtIndex(0).wireframe, + "No wireframe for the loaded entry after going back." + ); + + // Now forward again + loaded = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); + browser.goForward(); + await loaded; + + Assert.equal( + sh.count, + 2, + "Got the right SessionHistory entry count after going forward again." + ); + Assert.ok( + sh.getEntryAtIndex(0).wireframe, + "A wireframe was captured for the first entry after going forward again." + ); + Assert.ok( + !sh.getEntryAtIndex(1).wireframe, + "No wireframe for the loaded entry after going forward again." + ); + + // And using pushState + await SpecialPowers.spawn(browser, [], async () => { + content.history.pushState({}, "", "nothing-1.html"); + content.history.pushState({}, "", "nothing-2.html"); + }); + + Assert.equal( + sh.count, + 4, + "Got the right SessionHistory entry count after using pushState." + ); + Assert.ok( + sh.getEntryAtIndex(0).wireframe, + "A wireframe was captured for the first entry after using pushState." + ); + Assert.ok( + sh.getEntryAtIndex(1).wireframe, + "A wireframe was captured for the second entry after using pushState." + ); + Assert.ok( + sh.getEntryAtIndex(2).wireframe, + "A wireframe was captured for the third entry after using pushState." + ); + Assert.ok( + !sh.getEntryAtIndex(3).wireframe, + "No wireframe for the loaded entry after using pushState." + ); + + // Now check that wireframes can be written to in case we're restoring + // an nsISHEntry from serialization. + let wireframe = sh.getEntryAtIndex(2).wireframe; + sh.getEntryAtIndex(2).wireframe = null; + Assert.equal( + sh.getEntryAtIndex(2).wireframe, + null, + "Successfully cleared wireframe." + ); + + sh.getEntryAtIndex(3).wireframe = wireframe; + Assert.deepEqual( + sh.getEntryAtIndex(3).wireframe, + wireframe, + "Successfully wrote a wireframe to an nsISHEntry." + ); + }); +}); diff --git a/docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js b/docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js new file mode 100644 index 0000000000..a400d15466 --- /dev/null +++ b/docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_normal_and_history_loads() { + // This test is for the case when session history lives in the parent process. + // BFCache is disabled to get more asynchronousness. + await SpecialPowers.pushPrefEnv({ + set: [["fission.bfcacheInParent", false]], + }); + + let testPage = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com" + ) + "blank.html"; + await BrowserTestUtils.withNewTab( + { gBrowser, url: testPage }, + async function (browser) { + for (let i = 0; i < 2; ++i) { + BrowserTestUtils.loadURIString(browser, testPage + "?" + i); + await BrowserTestUtils.browserLoaded(browser); + } + + let sh = browser.browsingContext.sessionHistory; + is(sh.count, 3, "Should have 3 entries in the session history."); + is(sh.index, 2, "index should be 2"); + is(sh.requestedIndex, -1, "requestedIndex should be -1"); + + // The following part is racy by definition. It is testing that + // eventually requestedIndex should become -1 again. + browser.browsingContext.goToIndex(1); + let historyLoad = BrowserTestUtils.browserLoaded(browser); + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + await new Promise(r => { + setTimeout(r, 10); + }); + browser.browsingContext.loadURI( + Services.io.newURI(testPage + "?newload"), + { + triggeringPrincipal: browser.nodePrincipal, + } + ); + let newLoad = BrowserTestUtils.browserLoaded(browser); + // Note, the loads are racy. + await historyLoad; + await newLoad; + is(sh.requestedIndex, -1, "requestedIndex should be -1"); + + browser.browsingContext.goBack(); + await BrowserTestUtils.browserLoaded(browser); + is(sh.requestedIndex, -1, "requestedIndex should be -1"); + } + ); +}); diff --git a/docshell/test/navigation/bug343515_pg1.html b/docshell/test/navigation/bug343515_pg1.html new file mode 100644 index 0000000000..a8337c7f70 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg1.html @@ -0,0 +1,5 @@ + + + Page 1 + + diff --git a/docshell/test/navigation/bug343515_pg2.html b/docshell/test/navigation/bug343515_pg2.html new file mode 100644 index 0000000000..c5f5665de5 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg2.html @@ -0,0 +1,7 @@ + + + Page 2 + + + + diff --git a/docshell/test/navigation/bug343515_pg3.html b/docshell/test/navigation/bug343515_pg3.html new file mode 100644 index 0000000000..fdc79fbf7a --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3.html @@ -0,0 +1,7 @@ + + + Page 3 + + + + diff --git a/docshell/test/navigation/bug343515_pg3_1.html b/docshell/test/navigation/bug343515_pg3_1.html new file mode 100644 index 0000000000..254164c9f0 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3_1.html @@ -0,0 +1,6 @@ + + + pg3 - iframe 0 + + + diff --git a/docshell/test/navigation/bug343515_pg3_1_1.html b/docshell/test/navigation/bug343515_pg3_1_1.html new file mode 100644 index 0000000000..be05b74888 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3_1_1.html @@ -0,0 +1 @@ +How far does the rabbit hole go? diff --git a/docshell/test/navigation/bug343515_pg3_2.html b/docshell/test/navigation/bug343515_pg3_2.html new file mode 100644 index 0000000000..7655eb526d --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3_2.html @@ -0,0 +1 @@ +pg3 iframe 1 diff --git a/docshell/test/navigation/cache_control_max_age_3600.sjs b/docshell/test/navigation/cache_control_max_age_3600.sjs new file mode 100644 index 0000000000..49b6439c9f --- /dev/null +++ b/docshell/test/navigation/cache_control_max_age_3600.sjs @@ -0,0 +1,20 @@ +function handleRequest(request, response) { + let query = request.queryString; + let action = + query == "initial" + ? "cache_control_max_age_3600.sjs?second" + : "goback.html"; + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "max-age=3600"); + response.write( + "" + + '' + + "
" + + new Date().getTime() + + "
" + + "
" + + "" + ); +} diff --git a/docshell/test/navigation/file_beforeunload_and_bfcache.html b/docshell/test/navigation/file_beforeunload_and_bfcache.html new file mode 100644 index 0000000000..5c5aea2918 --- /dev/null +++ b/docshell/test/navigation/file_beforeunload_and_bfcache.html @@ -0,0 +1,31 @@ + + + + + + + diff --git a/docshell/test/navigation/file_blockBFCache.html b/docshell/test/navigation/file_blockBFCache.html new file mode 100644 index 0000000000..dc743cdc67 --- /dev/null +++ b/docshell/test/navigation/file_blockBFCache.html @@ -0,0 +1,33 @@ + diff --git a/docshell/test/navigation/file_bug1300461.html b/docshell/test/navigation/file_bug1300461.html new file mode 100644 index 0000000000..d7abe8be90 --- /dev/null +++ b/docshell/test/navigation/file_bug1300461.html @@ -0,0 +1,61 @@ + + + + + Bug 1300461 + + + + + diff --git a/docshell/test/navigation/file_bug1300461_back.html b/docshell/test/navigation/file_bug1300461_back.html new file mode 100644 index 0000000000..3ec431c7c1 --- /dev/null +++ b/docshell/test/navigation/file_bug1300461_back.html @@ -0,0 +1,37 @@ + + + + + Bug 1300461 + + + + + + diff --git a/docshell/test/navigation/file_bug1300461_redirect.html b/docshell/test/navigation/file_bug1300461_redirect.html new file mode 100644 index 0000000000..979530c5cf --- /dev/null +++ b/docshell/test/navigation/file_bug1300461_redirect.html @@ -0,0 +1,10 @@ + + + + + Bug 1300461 + + + Redirect to file_bug1300461_back.html. + + diff --git a/docshell/test/navigation/file_bug1300461_redirect.html^headers^ b/docshell/test/navigation/file_bug1300461_redirect.html^headers^ new file mode 100644 index 0000000000..241b891826 --- /dev/null +++ b/docshell/test/navigation/file_bug1300461_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: file_bug1300461_back.html diff --git a/docshell/test/navigation/file_bug1326251.html b/docshell/test/navigation/file_bug1326251.html new file mode 100644 index 0000000000..57a81f46f1 --- /dev/null +++ b/docshell/test/navigation/file_bug1326251.html @@ -0,0 +1,212 @@ + + + + + Bug 1326251 + + + +
+ +
+ + diff --git a/docshell/test/navigation/file_bug1326251_evict_cache.html b/docshell/test/navigation/file_bug1326251_evict_cache.html new file mode 100644 index 0000000000..b9873947f4 --- /dev/null +++ b/docshell/test/navigation/file_bug1326251_evict_cache.html @@ -0,0 +1,17 @@ + + + + + Bug 1326251 + + + + + diff --git a/docshell/test/navigation/file_bug1364364-1.html b/docshell/test/navigation/file_bug1364364-1.html new file mode 100644 index 0000000000..d4ecc42ad4 --- /dev/null +++ b/docshell/test/navigation/file_bug1364364-1.html @@ -0,0 +1,33 @@ + + + + + title + + +

+
+
+ + + diff --git a/docshell/test/navigation/file_bug1364364-2.html b/docshell/test/navigation/file_bug1364364-2.html new file mode 100644 index 0000000000..6e52ecaaa9 --- /dev/null +++ b/docshell/test/navigation/file_bug1364364-2.html @@ -0,0 +1,14 @@ + + + + + title + + + + + diff --git a/docshell/test/navigation/file_bug1375833-frame1.html b/docshell/test/navigation/file_bug1375833-frame1.html new file mode 100644 index 0000000000..ea38326479 --- /dev/null +++ b/docshell/test/navigation/file_bug1375833-frame1.html @@ -0,0 +1,8 @@ + + + + + iframe test page 1 + + iframe test page 1 + diff --git a/docshell/test/navigation/file_bug1375833-frame2.html b/docshell/test/navigation/file_bug1375833-frame2.html new file mode 100644 index 0000000000..6e76ab7e47 --- /dev/null +++ b/docshell/test/navigation/file_bug1375833-frame2.html @@ -0,0 +1,8 @@ + + + + + iframe test page 2 + + iframe test page 2 + diff --git a/docshell/test/navigation/file_bug1375833.html b/docshell/test/navigation/file_bug1375833.html new file mode 100644 index 0000000000..373a7fe08e --- /dev/null +++ b/docshell/test/navigation/file_bug1375833.html @@ -0,0 +1,22 @@ + + + + + Test for bug 1375833 + + + + + + diff --git a/docshell/test/navigation/file_bug1379762-1.html b/docshell/test/navigation/file_bug1379762-1.html new file mode 100644 index 0000000000..c8cd666667 --- /dev/null +++ b/docshell/test/navigation/file_bug1379762-1.html @@ -0,0 +1,35 @@ + + + + + Bug 1379762 + + + + diff --git a/docshell/test/navigation/file_bug1536471.html b/docshell/test/navigation/file_bug1536471.html new file mode 100644 index 0000000000..53012257ee --- /dev/null +++ b/docshell/test/navigation/file_bug1536471.html @@ -0,0 +1,8 @@ + + + Nested Frame +
+ +
+ + diff --git a/docshell/test/navigation/file_bug1583110.html b/docshell/test/navigation/file_bug1583110.html new file mode 100644 index 0000000000..5b08f54d21 --- /dev/null +++ b/docshell/test/navigation/file_bug1583110.html @@ -0,0 +1,26 @@ + + + + + + + diff --git a/docshell/test/navigation/file_bug1609475.html b/docshell/test/navigation/file_bug1609475.html new file mode 100644 index 0000000000..7699d46b08 --- /dev/null +++ b/docshell/test/navigation/file_bug1609475.html @@ -0,0 +1,51 @@ + + + + + + + + + \ No newline at end of file diff --git a/docshell/test/navigation/file_bug1706090.html b/docshell/test/navigation/file_bug1706090.html new file mode 100644 index 0000000000..9c5bc025d3 --- /dev/null +++ b/docshell/test/navigation/file_bug1706090.html @@ -0,0 +1,40 @@ + + + + + + + diff --git a/docshell/test/navigation/file_bug1745638.html b/docshell/test/navigation/file_bug1745638.html new file mode 100644 index 0000000000..b98c8de91f --- /dev/null +++ b/docshell/test/navigation/file_bug1745638.html @@ -0,0 +1,15 @@ + + + +The iframe below should not be blank after a reload.
+ + diff --git a/docshell/test/navigation/file_bug1750973.html b/docshell/test/navigation/file_bug1750973.html new file mode 100644 index 0000000000..28b2f995ae --- /dev/null +++ b/docshell/test/navigation/file_bug1750973.html @@ -0,0 +1,45 @@ + + + + + + + +
+ + +
+
+ + diff --git a/docshell/test/navigation/file_bug1758664.html b/docshell/test/navigation/file_bug1758664.html new file mode 100644 index 0000000000..07798dfddd --- /dev/null +++ b/docshell/test/navigation/file_bug1758664.html @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/docshell/test/navigation/file_bug386782_contenteditable.html b/docshell/test/navigation/file_bug386782_contenteditable.html new file mode 100644 index 0000000000..4515d015d9 --- /dev/null +++ b/docshell/test/navigation/file_bug386782_contenteditable.html @@ -0,0 +1 @@ +

contentEditable

\ No newline at end of file diff --git a/docshell/test/navigation/file_bug386782_designmode.html b/docshell/test/navigation/file_bug386782_designmode.html new file mode 100644 index 0000000000..faa063cbae --- /dev/null +++ b/docshell/test/navigation/file_bug386782_designmode.html @@ -0,0 +1 @@ +

designModeDocument

\ No newline at end of file diff --git a/docshell/test/navigation/file_bug462076_1.html b/docshell/test/navigation/file_bug462076_1.html new file mode 100644 index 0000000000..5050e79fdc --- /dev/null +++ b/docshell/test/navigation/file_bug462076_1.html @@ -0,0 +1,55 @@ + + + Bug 462076 + + + +
+
+
+
+ + + diff --git a/docshell/test/navigation/file_bug462076_2.html b/docshell/test/navigation/file_bug462076_2.html new file mode 100644 index 0000000000..63cf3de3f9 --- /dev/null +++ b/docshell/test/navigation/file_bug462076_2.html @@ -0,0 +1,52 @@ + + + Bug 462076 + + + +
+
+
+
+ + diff --git a/docshell/test/navigation/file_bug462076_3.html b/docshell/test/navigation/file_bug462076_3.html new file mode 100644 index 0000000000..5c779d2f49 --- /dev/null +++ b/docshell/test/navigation/file_bug462076_3.html @@ -0,0 +1,52 @@ + + + Bug 462076 + + + +
+
+
+
+ + diff --git a/docshell/test/navigation/file_bug508537_1.html b/docshell/test/navigation/file_bug508537_1.html new file mode 100644 index 0000000000..182c085670 --- /dev/null +++ b/docshell/test/navigation/file_bug508537_1.html @@ -0,0 +1,33 @@ + + + + + +
Container:
+
+
Original frames:
+ + + + + diff --git a/docshell/test/navigation/file_bug534178.html b/docshell/test/navigation/file_bug534178.html new file mode 100644 index 0000000000..4d77dd824b --- /dev/null +++ b/docshell/test/navigation/file_bug534178.html @@ -0,0 +1,30 @@ + + + + + + + diff --git a/docshell/test/navigation/file_contentpolicy_block_window.html b/docshell/test/navigation/file_contentpolicy_block_window.html new file mode 100644 index 0000000000..c51e574e5e --- /dev/null +++ b/docshell/test/navigation/file_contentpolicy_block_window.html @@ -0,0 +1,5 @@ + + +This window should never be openend! + + diff --git a/docshell/test/navigation/file_docshell_gotoindex.html b/docshell/test/navigation/file_docshell_gotoindex.html new file mode 100644 index 0000000000..f3e8919822 --- /dev/null +++ b/docshell/test/navigation/file_docshell_gotoindex.html @@ -0,0 +1,42 @@ + + + + + + 1 + 2 + + \ No newline at end of file diff --git a/docshell/test/navigation/file_document_write_1.html b/docshell/test/navigation/file_document_write_1.html new file mode 100644 index 0000000000..be52b60231 --- /dev/null +++ b/docshell/test/navigation/file_document_write_1.html @@ -0,0 +1,18 @@ + + + + + +
static content
+ + diff --git a/docshell/test/navigation/file_evict_from_bfcache.html b/docshell/test/navigation/file_evict_from_bfcache.html new file mode 100644 index 0000000000..9f50533543 --- /dev/null +++ b/docshell/test/navigation/file_evict_from_bfcache.html @@ -0,0 +1,29 @@ + + + + + + + diff --git a/docshell/test/navigation/file_fragment_handling_during_load.html b/docshell/test/navigation/file_fragment_handling_during_load.html new file mode 100644 index 0000000000..a7f468c32d --- /dev/null +++ b/docshell/test/navigation/file_fragment_handling_during_load.html @@ -0,0 +1,27 @@ + + + + + + + diff --git a/docshell/test/navigation/file_fragment_handling_during_load_frame1.html b/docshell/test/navigation/file_fragment_handling_during_load_frame1.html new file mode 100644 index 0000000000..c03ba2bda6 --- /dev/null +++ b/docshell/test/navigation/file_fragment_handling_during_load_frame1.html @@ -0,0 +1,6 @@ + + + +foo + + diff --git a/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs b/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs new file mode 100644 index 0000000000..77abe5949e --- /dev/null +++ b/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs @@ -0,0 +1,20 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */ +"use strict"; + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "no-cache", false); + // Wait a bit. + var s = Date.now(); + // eslint-disable-next-line no-empty + while (Date.now() - s < 1000) {} + + response.write(` + + + bar + + + `); +} diff --git a/docshell/test/navigation/file_load_history_entry_page_with_one_link.html b/docshell/test/navigation/file_load_history_entry_page_with_one_link.html new file mode 100644 index 0000000000..a4d1b62176 --- /dev/null +++ b/docshell/test/navigation/file_load_history_entry_page_with_one_link.html @@ -0,0 +1,7 @@ + + + +Link 1 +link 1 + + diff --git a/docshell/test/navigation/file_load_history_entry_page_with_two_links.html b/docshell/test/navigation/file_load_history_entry_page_with_two_links.html new file mode 100644 index 0000000000..4be2ea6f4e --- /dev/null +++ b/docshell/test/navigation/file_load_history_entry_page_with_two_links.html @@ -0,0 +1,9 @@ + + + +Link 1 +Link 2 +link 1 +link 2 + + diff --git a/docshell/test/navigation/file_meta_refresh.html b/docshell/test/navigation/file_meta_refresh.html new file mode 100644 index 0000000000..2a06cc5acf --- /dev/null +++ b/docshell/test/navigation/file_meta_refresh.html @@ -0,0 +1,40 @@ + + + + + + + diff --git a/docshell/test/navigation/file_navigation_type.html b/docshell/test/navigation/file_navigation_type.html new file mode 100644 index 0000000000..bb538eefec --- /dev/null +++ b/docshell/test/navigation/file_navigation_type.html @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/docshell/test/navigation/file_nested_frames.html b/docshell/test/navigation/file_nested_frames.html new file mode 100644 index 0000000000..6ec286aa3e --- /dev/null +++ b/docshell/test/navigation/file_nested_frames.html @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/docshell/test/navigation/file_nested_frames_innerframe.html b/docshell/test/navigation/file_nested_frames_innerframe.html new file mode 100644 index 0000000000..e25b6a4f6a --- /dev/null +++ b/docshell/test/navigation/file_nested_frames_innerframe.html @@ -0,0 +1 @@ + diff --git a/docshell/test/navigation/file_nested_srcdoc.html b/docshell/test/navigation/file_nested_srcdoc.html new file mode 100644 index 0000000000..ae6d656f27 --- /dev/null +++ b/docshell/test/navigation/file_nested_srcdoc.html @@ -0,0 +1,3 @@ + +iframe loaded inside of a srcdoc + diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html new file mode 100644 index 0000000000..2f9a41e1d1 --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html @@ -0,0 +1,5 @@ + diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^ b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html new file mode 100644 index 0000000000..de456f8f1c --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html @@ -0,0 +1,10 @@ + diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^ b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html new file mode 100644 index 0000000000..2237e3e367 --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html @@ -0,0 +1,22 @@ + diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^ b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_4.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_4.html new file mode 100644 index 0000000000..d5c3f61794 --- /dev/null +++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_4.html @@ -0,0 +1,16 @@ + diff --git a/docshell/test/navigation/file_online_offline_bfcache.html b/docshell/test/navigation/file_online_offline_bfcache.html new file mode 100644 index 0000000000..7f8138e758 --- /dev/null +++ b/docshell/test/navigation/file_online_offline_bfcache.html @@ -0,0 +1,41 @@ + + + + + + + diff --git a/docshell/test/navigation/file_reload.html b/docshell/test/navigation/file_reload.html new file mode 100644 index 0000000000..f0cb1c2d52 --- /dev/null +++ b/docshell/test/navigation/file_reload.html @@ -0,0 +1,23 @@ + + + + + + + diff --git a/docshell/test/navigation/file_reload_large_postdata.sjs b/docshell/test/navigation/file_reload_large_postdata.sjs new file mode 100644 index 0000000000..385d43d99f --- /dev/null +++ b/docshell/test/navigation/file_reload_large_postdata.sjs @@ -0,0 +1,46 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */ +"use strict"; + +const BinaryInputStream = Components.Constructor( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +Cu.importGlobalProperties(["URLSearchParams"]); + +function readStream(inputStream) { + let available = 0; + let result = []; + while ((available = inputStream.available()) > 0) { + result.push(inputStream.readBytes(available)); + } + return result.join(""); +} + +function handleRequest(request, response) { + let rv = (() => { + try { + if (request.method != "POST") { + return "ERROR: not a POST request"; + } + + let body = new URLSearchParams( + readStream(new BinaryInputStream(request.bodyInputStream)) + ); + return body.get("payload").length; + } catch (e) { + return "ERROR: Exception: " + e; + } + })(); + + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "no-cache", false); + response.write(` + + `); +} diff --git a/docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs b/docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs new file mode 100644 index 0000000000..e1ef75efa0 --- /dev/null +++ b/docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs @@ -0,0 +1,27 @@ +const createPage = function (msg) { + return ` + + + + +`; +}; + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-store"); + response.setHeader("Content-Type", "text/html"); + + let currentState = getState("reload_nonbfcached_srcdoc"); + let srcdoc = "pageload:" + currentState; + if (currentState != "second") { + setState("reload_nonbfcached_srcdoc", "second"); + } else { + setState("reload_nonbfcached_srcdoc", ""); + } + + response.write(createPage(srcdoc)); +} diff --git a/docshell/test/navigation/file_same_url.html b/docshell/test/navigation/file_same_url.html new file mode 100644 index 0000000000..72a1dd2564 --- /dev/null +++ b/docshell/test/navigation/file_same_url.html @@ -0,0 +1,24 @@ + + + + + + +Link 1 +link 1 + + diff --git a/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html new file mode 100644 index 0000000000..fec51f821d --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html @@ -0,0 +1,30 @@ + + + + + + + diff --git a/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html new file mode 100644 index 0000000000..40e0578515 --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html @@ -0,0 +1,35 @@ + + + + + +
content
+ + diff --git a/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/navigation/file_scrollRestoration_navigate.html b/docshell/test/navigation/file_scrollRestoration_navigate.html new file mode 100644 index 0000000000..ac78f0abbe --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_navigate.html @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html new file mode 100644 index 0000000000..1c94899ac2 --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html @@ -0,0 +1,63 @@ + + + + + +
+  
+
Hello world
+ hash + + diff --git a/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^ b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^ new file mode 100644 index 0000000000..f944e3806d --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store \ No newline at end of file diff --git a/docshell/test/navigation/file_scrollRestoration_part2_bfcache.html b/docshell/test/navigation/file_scrollRestoration_part2_bfcache.html new file mode 100644 index 0000000000..2776e42a6e --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_part2_bfcache.html @@ -0,0 +1,57 @@ + + + + + +
+  
+
Hello world
+ hash + + diff --git a/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html new file mode 100644 index 0000000000..ffc68d6ccc --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html @@ -0,0 +1,157 @@ + + + + + +
+  
+
Hello world
+ hash + + diff --git a/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^ b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^ new file mode 100644 index 0000000000..f944e3806d --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store \ No newline at end of file diff --git a/docshell/test/navigation/file_session_history_on_redirect.html b/docshell/test/navigation/file_session_history_on_redirect.html new file mode 100644 index 0000000000..df7e99a1dd --- /dev/null +++ b/docshell/test/navigation/file_session_history_on_redirect.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/docshell/test/navigation/file_session_history_on_redirect.html^headers^ b/docshell/test/navigation/file_session_history_on_redirect.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/navigation/file_session_history_on_redirect.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/navigation/file_session_history_on_redirect_2.html b/docshell/test/navigation/file_session_history_on_redirect_2.html new file mode 100644 index 0000000000..df7e99a1dd --- /dev/null +++ b/docshell/test/navigation/file_session_history_on_redirect_2.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/docshell/test/navigation/file_session_history_on_redirect_2.html^headers^ b/docshell/test/navigation/file_session_history_on_redirect_2.html^headers^ new file mode 100644 index 0000000000..59ba296103 --- /dev/null +++ b/docshell/test/navigation/file_session_history_on_redirect_2.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/navigation/file_sessionhistory_iframe_removal.html b/docshell/test/navigation/file_sessionhistory_iframe_removal.html new file mode 100644 index 0000000000..f18e849942 --- /dev/null +++ b/docshell/test/navigation/file_sessionhistory_iframe_removal.html @@ -0,0 +1,37 @@ + + + + + + + + + diff --git a/docshell/test/navigation/file_shiftReload_and_pushState.html b/docshell/test/navigation/file_shiftReload_and_pushState.html new file mode 100644 index 0000000000..7882143c83 --- /dev/null +++ b/docshell/test/navigation/file_shiftReload_and_pushState.html @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/docshell/test/navigation/file_ship_beforeunload_fired.html b/docshell/test/navigation/file_ship_beforeunload_fired.html new file mode 100644 index 0000000000..a1f416f959 --- /dev/null +++ b/docshell/test/navigation/file_ship_beforeunload_fired.html @@ -0,0 +1,37 @@ + + + diff --git a/docshell/test/navigation/file_static_and_dynamic_1.html b/docshell/test/navigation/file_static_and_dynamic_1.html new file mode 100644 index 0000000000..e66216c41e --- /dev/null +++ b/docshell/test/navigation/file_static_and_dynamic_1.html @@ -0,0 +1,31 @@ + + + + + +
Dynamic
+
+
Static
+
+ + diff --git a/docshell/test/navigation/file_tell_opener.html b/docshell/test/navigation/file_tell_opener.html new file mode 100644 index 0000000000..bd45c275e6 --- /dev/null +++ b/docshell/test/navigation/file_tell_opener.html @@ -0,0 +1,8 @@ + + Frame 1 + + diff --git a/docshell/test/navigation/file_triggeringprincipal_frame_1.html b/docshell/test/navigation/file_triggeringprincipal_frame_1.html new file mode 100644 index 0000000000..528437f892 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_frame_1.html @@ -0,0 +1,27 @@ + + + + +Frame 1
+click me + + + + + diff --git a/docshell/test/navigation/file_triggeringprincipal_frame_2.html b/docshell/test/navigation/file_triggeringprincipal_frame_2.html new file mode 100644 index 0000000000..ef7cdfc178 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_frame_2.html @@ -0,0 +1,8 @@ + + + + +Frame 2
+ + + diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html new file mode 100644 index 0000000000..75b2933c1b --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html @@ -0,0 +1,6 @@ + + + +Frame A + + diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html new file mode 100644 index 0000000000..0479f5e1e5 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html @@ -0,0 +1,6 @@ + + + +Frame A navigated by Frame B + + diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html new file mode 100644 index 0000000000..e5d40b267a --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html @@ -0,0 +1,15 @@ + + + +Frame B navigating Frame A + + + + + + + diff --git a/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html new file mode 100644 index 0000000000..caa6b275b9 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html @@ -0,0 +1,6 @@ + + + +base test frame + + diff --git a/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html new file mode 100644 index 0000000000..f4a4d0e631 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html @@ -0,0 +1,6 @@ + + + +navigated by window.open() + + diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe.html b/docshell/test/navigation/file_triggeringprincipal_subframe.html new file mode 100644 index 0000000000..ba6b6dc09a --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_subframe.html @@ -0,0 +1,15 @@ + + + + +Sub Frame 2
+ + + diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html b/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html new file mode 100644 index 0000000000..582181c00d --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html @@ -0,0 +1,21 @@ + + + + +Sub Frame 2 Navigated
+ + + + diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html b/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html new file mode 100644 index 0000000000..c84e216ae8 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html @@ -0,0 +1,20 @@ + + + + +SubFrame Same-Origin Navigated
+ + + + diff --git a/docshell/test/navigation/file_triggeringprincipal_window_open.html b/docshell/test/navigation/file_triggeringprincipal_window_open.html new file mode 100644 index 0000000000..d0644a4d5c --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_window_open.html @@ -0,0 +1,6 @@ + + + +http + + diff --git a/docshell/test/navigation/frame0.html b/docshell/test/navigation/frame0.html new file mode 100644 index 0000000000..93d1c9c822 --- /dev/null +++ b/docshell/test/navigation/frame0.html @@ -0,0 +1,3 @@ + + Frame 0 + diff --git a/docshell/test/navigation/frame1.html b/docshell/test/navigation/frame1.html new file mode 100644 index 0000000000..4d06c09d1c --- /dev/null +++ b/docshell/test/navigation/frame1.html @@ -0,0 +1,3 @@ + + Frame 1 + diff --git a/docshell/test/navigation/frame2.html b/docshell/test/navigation/frame2.html new file mode 100644 index 0000000000..7a3b5e0b9b --- /dev/null +++ b/docshell/test/navigation/frame2.html @@ -0,0 +1,3 @@ + + Frame 2 + diff --git a/docshell/test/navigation/frame3.html b/docshell/test/navigation/frame3.html new file mode 100644 index 0000000000..fd24293873 --- /dev/null +++ b/docshell/test/navigation/frame3.html @@ -0,0 +1,3 @@ + + Frame 3 + diff --git a/docshell/test/navigation/frame_1_out_of_6.html b/docshell/test/navigation/frame_1_out_of_6.html new file mode 100644 index 0000000000..93547cd1c4 --- /dev/null +++ b/docshell/test/navigation/frame_1_out_of_6.html @@ -0,0 +1,6 @@ + + + Page 1 + + + \ No newline at end of file diff --git a/docshell/test/navigation/frame_2_out_of_6.html b/docshell/test/navigation/frame_2_out_of_6.html new file mode 100644 index 0000000000..02056acce8 --- /dev/null +++ b/docshell/test/navigation/frame_2_out_of_6.html @@ -0,0 +1,6 @@ + + + Page 2 + + + \ No newline at end of file diff --git a/docshell/test/navigation/frame_3_out_of_6.html b/docshell/test/navigation/frame_3_out_of_6.html new file mode 100644 index 0000000000..e9dc308f6e --- /dev/null +++ b/docshell/test/navigation/frame_3_out_of_6.html @@ -0,0 +1,6 @@ + + + Page 3 + + + \ No newline at end of file diff --git a/docshell/test/navigation/frame_4_out_of_6.html b/docshell/test/navigation/frame_4_out_of_6.html new file mode 100644 index 0000000000..66a5083e4f --- /dev/null +++ b/docshell/test/navigation/frame_4_out_of_6.html @@ -0,0 +1,6 @@ + + + Page 4 + + + \ No newline at end of file diff --git a/docshell/test/navigation/frame_5_out_of_6.html b/docshell/test/navigation/frame_5_out_of_6.html new file mode 100644 index 0000000000..0121f0f749 --- /dev/null +++ b/docshell/test/navigation/frame_5_out_of_6.html @@ -0,0 +1,6 @@ + + + Page 5 + + + \ No newline at end of file diff --git a/docshell/test/navigation/frame_6_out_of_6.html b/docshell/test/navigation/frame_6_out_of_6.html new file mode 100644 index 0000000000..c9827ccaae --- /dev/null +++ b/docshell/test/navigation/frame_6_out_of_6.html @@ -0,0 +1,6 @@ + + + Page 6 + + + \ No newline at end of file diff --git a/docshell/test/navigation/frame_load_as_example_com.html b/docshell/test/navigation/frame_load_as_example_com.html new file mode 100644 index 0000000000..a1a4e7110a --- /dev/null +++ b/docshell/test/navigation/frame_load_as_example_com.html @@ -0,0 +1,6 @@ + + + example.com + + + diff --git a/docshell/test/navigation/frame_load_as_example_org.html b/docshell/test/navigation/frame_load_as_example_org.html new file mode 100644 index 0000000000..2fbb8038c9 --- /dev/null +++ b/docshell/test/navigation/frame_load_as_example_org.html @@ -0,0 +1,6 @@ + + + example.org + + + diff --git a/docshell/test/navigation/frame_load_as_host1.html b/docshell/test/navigation/frame_load_as_host1.html new file mode 100644 index 0000000000..eb006b21a1 --- /dev/null +++ b/docshell/test/navigation/frame_load_as_host1.html @@ -0,0 +1,6 @@ + + + Page 1 + + + diff --git a/docshell/test/navigation/frame_load_as_host2.html b/docshell/test/navigation/frame_load_as_host2.html new file mode 100644 index 0000000000..5457c17e9b --- /dev/null +++ b/docshell/test/navigation/frame_load_as_host2.html @@ -0,0 +1,6 @@ + + + Page 2 + + + diff --git a/docshell/test/navigation/frame_load_as_host3.html b/docshell/test/navigation/frame_load_as_host3.html new file mode 100644 index 0000000000..a9064ec867 --- /dev/null +++ b/docshell/test/navigation/frame_load_as_host3.html @@ -0,0 +1,6 @@ + + + Page 3 + + + diff --git a/docshell/test/navigation/frame_recursive.html b/docshell/test/navigation/frame_recursive.html new file mode 100644 index 0000000000..835d9d63a2 --- /dev/null +++ b/docshell/test/navigation/frame_recursive.html @@ -0,0 +1,6 @@ + + + example.com + + + diff --git a/docshell/test/navigation/goback.html b/docshell/test/navigation/goback.html new file mode 100644 index 0000000000..ce2968374e --- /dev/null +++ b/docshell/test/navigation/goback.html @@ -0,0 +1,5 @@ + + + window.history.go(-1); + + diff --git a/docshell/test/navigation/iframe.html b/docshell/test/navigation/iframe.html new file mode 100644 index 0000000000..f8fce53c55 --- /dev/null +++ b/docshell/test/navigation/iframe.html @@ -0,0 +1,9 @@ + + + + + diff --git a/docshell/test/navigation/iframe_slow_onload.html b/docshell/test/navigation/iframe_slow_onload.html new file mode 100644 index 0000000000..e8555699bb --- /dev/null +++ b/docshell/test/navigation/iframe_slow_onload.html @@ -0,0 +1,5 @@ + + + + + + diff --git a/docshell/test/navigation/mochitest.ini b/docshell/test/navigation/mochitest.ini new file mode 100644 index 0000000000..1c4fe28309 --- /dev/null +++ b/docshell/test/navigation/mochitest.ini @@ -0,0 +1,255 @@ +[DEFAULT] +support-files = + NavigationUtils.js + navigation_target_url.html + navigation_target_popup_url.html + blank.html + file_bug386782_contenteditable.html + file_bug386782_designmode.html + redbox_bug430723.html + bluebox_bug430723.html + file_bug462076_1.html + file_bug462076_2.html + file_bug462076_3.html + file_bug508537_1.html + file_bug534178.html + file_document_write_1.html + file_fragment_handling_during_load.html + file_fragment_handling_during_load_frame1.html + file_fragment_handling_during_load_frame2.sjs + file_nested_frames.html + file_nested_frames_innerframe.html + file_shiftReload_and_pushState.html + file_static_and_dynamic_1.html + frame0.html + frame1.html + frame2.html + frame3.html + goback.html + iframe.html + iframe_static.html + navigate.html + open.html + parent.html + file_tell_opener.html + file_triggeringprincipal_frame_1.html + file_triggeringprincipal_frame_2.html + file_triggeringprincipal_subframe.html + file_triggeringprincipal_subframe_nav.html + file_triggeringprincipal_subframe_same_origin_nav.html + file_triggeringprincipal_window_open.html + file_triggeringprincipal_parent_iframe_window_open_base.html + file_triggeringprincipal_parent_iframe_window_open_nav.html + file_triggeringprincipal_iframe_iframe_window_open_frame_a.html + file_triggeringprincipal_iframe_iframe_window_open_frame_b.html + file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html + file_load_history_entry_page_with_one_link.html + file_load_history_entry_page_with_two_links.html + file_bug1300461.html + file_bug1300461_redirect.html + file_bug1300461_redirect.html^headers^ + file_bug1300461_back.html + file_contentpolicy_block_window.html + file_bug1326251.html + file_bug1326251_evict_cache.html + file_bug1364364-1.html + file_bug1364364-2.html + file_bug1375833.html + file_bug1375833-frame1.html + file_bug1375833-frame2.html + test_bug145971.html + file_bug1609475.html + file_bug1379762-1.html + file_scrollRestoration_bfcache_and_nobfcache.html + file_scrollRestoration_bfcache_and_nobfcache_part2.html + file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^ + file_scrollRestoration_navigate.html + file_scrollRestoration_part1_nobfcache.html + file_scrollRestoration_part1_nobfcache.html^headers^ + file_scrollRestoration_part2_bfcache.html + file_scrollRestoration_part3_nobfcache.html + file_scrollRestoration_part3_nobfcache.html^headers^ + file_session_history_on_redirect.html + file_session_history_on_redirect.html^headers^ + file_session_history_on_redirect_2.html + file_session_history_on_redirect_2.html^headers^ + redirect_handlers.sjs + frame_load_as_example_com.html + frame_load_as_example_org.html + frame_load_as_host1.html + frame_load_as_host2.html + frame_load_as_host3.html + frame_1_out_of_6.html + frame_2_out_of_6.html + frame_3_out_of_6.html + frame_4_out_of_6.html + frame_5_out_of_6.html + frame_6_out_of_6.html + frame_recursive.html + object_recursive_load.html + file_nested_srcdoc.html + +[test_aboutblank_change_process.html] +[test_beforeunload_and_bfcache.html] +support-files = file_beforeunload_and_bfcache.html +[test_bug13871.html] +skip-if = + http3 +[test_bug1583110.html] +support-files = file_bug1583110.html +[test_bug1706090.html] +support-files = file_bug1706090.html +skip-if = sessionHistoryInParent # The test is currently for the old bfcache implementation +[test_bug1745638.html] +support-files = file_bug1745638.html +[test_bug1747019.html] +support-files = + goback.html + cache_control_max_age_3600.sjs +[test_bug1750973.html] +support-files = file_bug1750973.html +[test_bug1758664.html] +support-files = file_bug1758664.html +skip-if = !sessionHistoryInParent # the old implementation behaves inconsistently +[test_bug270414.html] +skip-if = + http3 +[test_bug278916.html] +[test_bug279495.html] +[test_bug344861.html] +skip-if = toolkit == "android" +[test_bug386782.html] +[test_bug430624.html] +[test_bug430723.html] +skip-if = + (!debug && (os == 'mac' || os == 'win')) # Bug 874423 + http3 +[test_bug1364364.html] +skip-if = (os == "android") # Bug 1560378 +[test_bug1375833.html] +[test_bug1536471.html] +support-files = file_bug1536471.html +[test_blockBFCache.html] +support-files = + file_blockBFCache.html + slow.sjs + iframe_slow_onload.html + iframe_slow_onload_inner.html +skip-if = + http3 +[test_child.html] +[test_docshell_gotoindex.html] +support-files = file_docshell_gotoindex.html +[test_evict_from_bfcache.html] +support-files = file_evict_from_bfcache.html +[test_grandchild.html] +skip-if = + http3 +[test_load_history_entry.html] +[test_meta_refresh.html] +support-files = file_meta_refresh.html +[test_navigation_type.html] +support-files = file_navigation_type.html +[test_new_shentry_during_history_navigation.html] +support-files = + file_new_shentry_during_history_navigation_1.html + file_new_shentry_during_history_navigation_1.html^headers^ + file_new_shentry_during_history_navigation_2.html + file_new_shentry_during_history_navigation_2.html^headers^ + file_new_shentry_during_history_navigation_3.html + file_new_shentry_during_history_navigation_3.html^headers^ + file_new_shentry_during_history_navigation_4.html +[test_not-opener.html] +skip-if = + http3 +[test_online_offline_bfcache.html] +support-files = file_online_offline_bfcache.html +[test_opener.html] +skip-if = + fission && xorigin # Bug 1716402 - New fission platform triage + os == "linux" && bits == 64 # Bug 1572299 + win10_2004 # Bug 1572299 + win11_2009 # Bug 1797751 + fission && os == "android" # Bug 1827323 +[test_popup-navigates-children.html] +skip-if = + http3 +[test_reload.html] +support-files = file_reload.html +[test_reload_nonbfcached_srcdoc.html] +support-files = file_reload_nonbfcached_srcdoc.sjs +skip-if = + http3 +[test_reserved.html] +skip-if = + debug # bug 1263213 +[test_performance_navigation.html] +[test_same_url.html] +support-files = file_same_url.html +[test_session_history_on_redirect.html] +[test_sessionhistory.html] +skip-if = verify && (os == 'mac') && debug # Hit MOZ_CRASH(Shutdown too long, probably frozen, causing a crash.) bug 1677545 +[test_dynamic_frame_forward_back.html] +[test_sessionhistory_document_write.html] +[test_sessionhistory_iframe_removal.html] +support-files = file_sessionhistory_iframe_removal.html +[test_session_history_entry_cleanup.html] +[test_fragment_handling_during_load.html] +[test_nested_frames.html] +skip-if = + http3 +[test_shiftReload_and_pushState.html] +[test_scrollRestoration.html] +[test_bug1609475.html] +[test_bug1300461.html] +[test_bug1326251.html] +skip-if = toolkit == 'android' || sessionHistoryInParent # It relies on the bfcache +[test_bug1379762.html] +[test_state_size.html] +[test_static_and_dynamic.html] +skip-if = true # This was disabled for a few years now anyway, bug 1677544 +[test_sibling-matching-parent.html] +[test_sibling-off-domain.html] +skip-if = + http3 +[test_triggeringprincipal_frame_nav.html] +skip-if = + http3 +[test_triggeringprincipal_frame_same_origin_nav.html] +skip-if = + http3 +[test_triggeringprincipal_window_open.html] +skip-if = + http3 +[test_triggeringprincipal_parent_iframe_window_open.html] +skip-if = + http3 +[test_triggeringprincipal_iframe_iframe_window_open.html] +skip-if = + http3 +[test_contentpolicy_block_window.html] +[test_rate_limit_location_change.html] +[test_reload_large_postdata.html] +support-files = + file_reload_large_postdata.sjs +[test_recursive_frames.html] +skip-if = + http3 + fission && os == "android" # Bug 1714698 +[test_bug1699721.html] +skip-if = !fission # tests fission-only process switching behaviour +[test_ship_beforeunload_fired.html] +support-files = + file_ship_beforeunload_fired.html +skip-if = + !sessionHistoryInParent + http3 +[test_ship_beforeunload_fired_2.html] +support-files = + file_ship_beforeunload_fired.html +skip-if = !sessionHistoryInParent +[test_ship_beforeunload_fired_3.html] +support-files = + file_ship_beforeunload_fired.html +skip-if = !sessionHistoryInParent +[test_open_javascript_noopener.html] diff --git a/docshell/test/navigation/navigate.html b/docshell/test/navigation/navigate.html new file mode 100644 index 0000000000..f68123188e --- /dev/null +++ b/docshell/test/navigation/navigate.html @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/docshell/test/navigation/navigation_target_popup_url.html b/docshell/test/navigation/navigation_target_popup_url.html new file mode 100644 index 0000000000..cfe6de009d --- /dev/null +++ b/docshell/test/navigation/navigation_target_popup_url.html @@ -0,0 +1 @@ +This is a popup diff --git a/docshell/test/navigation/navigation_target_url.html b/docshell/test/navigation/navigation_target_url.html new file mode 100644 index 0000000000..a485e8133f --- /dev/null +++ b/docshell/test/navigation/navigation_target_url.html @@ -0,0 +1 @@ +This frame was navigated. diff --git a/docshell/test/navigation/object_recursive_load.html b/docshell/test/navigation/object_recursive_load.html new file mode 100644 index 0000000000..3ae9521e63 --- /dev/null +++ b/docshell/test/navigation/object_recursive_load.html @@ -0,0 +1,6 @@ + + + Frame 0 + + + diff --git a/docshell/test/navigation/open.html b/docshell/test/navigation/open.html new file mode 100644 index 0000000000..9a96a8dda7 --- /dev/null +++ b/docshell/test/navigation/open.html @@ -0,0 +1,10 @@ + + + + + diff --git a/docshell/test/navigation/parent.html b/docshell/test/navigation/parent.html new file mode 100644 index 0000000000..74722b8bdf --- /dev/null +++ b/docshell/test/navigation/parent.html @@ -0,0 +1,14 @@ + + + +This document contains a frame. +
+ + + + diff --git a/docshell/test/navigation/redbox_bug430723.html b/docshell/test/navigation/redbox_bug430723.html new file mode 100644 index 0000000000..c2d1f98092 --- /dev/null +++ b/docshell/test/navigation/redbox_bug430723.html @@ -0,0 +1,6 @@ + + + +
+

This is a very tall red box.

+
diff --git a/docshell/test/navigation/redirect_handlers.sjs b/docshell/test/navigation/redirect_handlers.sjs new file mode 100644 index 0000000000..c2b39e61c9 --- /dev/null +++ b/docshell/test/navigation/redirect_handlers.sjs @@ -0,0 +1,29 @@ +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Cache-Control", "no-store", false); + + let state = getState("sessionhistory_do_redirect"); + if (state != "doredirect") { + response.setHeader("Content-Type", "text/html"); + const contents = ` + + `; + response.write(contents); + + // The next load should do a redirect. + setState("sessionhistory_do_redirect", "doredirect"); + } else { + setState("sessionhistory_do_redirect", ""); + + response.setStatusLine("1.1", 302, "Found"); + response.setHeader( + "Location", + "file_session_history_on_redirect_2.html", + false + ); + } +} diff --git a/docshell/test/navigation/redirect_to_blank.sjs b/docshell/test/navigation/redirect_to_blank.sjs new file mode 100644 index 0000000000..b1668401ea --- /dev/null +++ b/docshell/test/navigation/redirect_to_blank.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Cache-Control", "no-store", false); + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", "blank.html", false); +} diff --git a/docshell/test/navigation/slow.sjs b/docshell/test/navigation/slow.sjs new file mode 100644 index 0000000000..9720f807f6 --- /dev/null +++ b/docshell/test/navigation/slow.sjs @@ -0,0 +1,16 @@ +function handleRequest(request, response) { + response.processAsync(); + + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init( + function () { + response.finish(); + }, + 5000, + Ci.nsITimer.TYPE_ONE_SHOT + ); + + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "text/plain", false); + response.write("Start of the content."); +} diff --git a/docshell/test/navigation/test_aboutblank_change_process.html b/docshell/test/navigation/test_aboutblank_change_process.html new file mode 100644 index 0000000000..61325570f3 --- /dev/null +++ b/docshell/test/navigation/test_aboutblank_change_process.html @@ -0,0 +1,46 @@ + + + + + + + diff --git a/docshell/test/navigation/test_beforeunload_and_bfcache.html b/docshell/test/navigation/test_beforeunload_and_bfcache.html new file mode 100644 index 0000000000..6bb958c6c6 --- /dev/null +++ b/docshell/test/navigation/test_beforeunload_and_bfcache.html @@ -0,0 +1,97 @@ + + + + + Loading a page from BFCache and firing beforeunload on the current page + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_blockBFCache.html b/docshell/test/navigation/test_blockBFCache.html
new file mode 100644
index 0000000000..3d4a418369
--- /dev/null
+++ b/docshell/test/navigation/test_blockBFCache.html
@@ -0,0 +1,294 @@
+
+
+
+  
+  Blocking pages from entering BFCache
+  
+  
+
+
+
+
+
diff --git a/docshell/test/navigation/test_bug1300461.html b/docshell/test/navigation/test_bug1300461.html
new file mode 100644
index 0000000000..22783c07c2
--- /dev/null
+++ b/docshell/test/navigation/test_bug1300461.html
@@ -0,0 +1,70 @@
+
+
+
+
+  Test for Bug 1300461
+  
+  
+
+
+Mozilla Bug 1300461
+

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_bug1326251.html b/docshell/test/navigation/test_bug1326251.html new file mode 100644 index 0000000000..3c951729e6 --- /dev/null +++ b/docshell/test/navigation/test_bug1326251.html @@ -0,0 +1,47 @@ + + + + + Test for Bug 1326251 + + + + +Mozilla Bug 1326251 +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_bug1364364.html b/docshell/test/navigation/test_bug1364364.html new file mode 100644 index 0000000000..90d2009df4 --- /dev/null +++ b/docshell/test/navigation/test_bug1364364.html @@ -0,0 +1,65 @@ + + + + + + Test for Bug 1364364 + + + + + +Mozilla Bug 1364364 +

+ +
+
+ + diff --git a/docshell/test/navigation/test_bug1375833.html b/docshell/test/navigation/test_bug1375833.html new file mode 100644 index 0000000000..c2a7750a4e --- /dev/null +++ b/docshell/test/navigation/test_bug1375833.html @@ -0,0 +1,131 @@ + + + + + + Test for Bug 1375833 + + + + + +Mozilla Bug 1375833 +

+ +
+
+ + diff --git a/docshell/test/navigation/test_bug1379762.html b/docshell/test/navigation/test_bug1379762.html new file mode 100644 index 0000000000..eda3b539a5 --- /dev/null +++ b/docshell/test/navigation/test_bug1379762.html @@ -0,0 +1,67 @@ + + + + + Test for Bug 1379762 + + + + +Mozilla Bug 1379762 +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_bug13871.html b/docshell/test/navigation/test_bug13871.html new file mode 100644 index 0000000000..0532bc7b56 --- /dev/null +++ b/docshell/test/navigation/test_bug13871.html @@ -0,0 +1,85 @@ + + + + + + + + + + + +Mozilla Bug 13871 +
+
+
+ + diff --git a/docshell/test/navigation/test_bug145971.html b/docshell/test/navigation/test_bug145971.html new file mode 100644 index 0000000000..ffad27a9c3 --- /dev/null +++ b/docshell/test/navigation/test_bug145971.html @@ -0,0 +1,29 @@ + + + + + + Testing bug 145971. + + diff --git a/docshell/test/navigation/test_bug1536471.html b/docshell/test/navigation/test_bug1536471.html new file mode 100644 index 0000000000..f37aedba21 --- /dev/null +++ b/docshell/test/navigation/test_bug1536471.html @@ -0,0 +1,75 @@ + + + + + + Test for Bug 1536471 + + + + + +Mozilla Bug +

+ +
+
+
+ + + + diff --git a/docshell/test/navigation/test_bug1583110.html b/docshell/test/navigation/test_bug1583110.html new file mode 100644 index 0000000000..f1c1b65e4d --- /dev/null +++ b/docshell/test/navigation/test_bug1583110.html @@ -0,0 +1,36 @@ + + + + + test bug 1583110 + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_bug1609475.html b/docshell/test/navigation/test_bug1609475.html
new file mode 100644
index 0000000000..4dbe7d17d6
--- /dev/null
+++ b/docshell/test/navigation/test_bug1609475.html
@@ -0,0 +1,35 @@
+
+
+
+
+  Test for Bug 1609475
+  
+  
+
+
+Mozilla Bug 1609475
+

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_bug1699721.html b/docshell/test/navigation/test_bug1699721.html new file mode 100644 index 0000000000..687c5306cf --- /dev/null +++ b/docshell/test/navigation/test_bug1699721.html @@ -0,0 +1,110 @@ + + + + + + + +
+
+
+ + diff --git a/docshell/test/navigation/test_bug1706090.html b/docshell/test/navigation/test_bug1706090.html new file mode 100644 index 0000000000..293148b9c6 --- /dev/null +++ b/docshell/test/navigation/test_bug1706090.html @@ -0,0 +1,49 @@ + + + + + Bug 1706090 + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_bug1745638.html b/docshell/test/navigation/test_bug1745638.html
new file mode 100644
index 0000000000..594c464da3
--- /dev/null
+++ b/docshell/test/navigation/test_bug1745638.html
@@ -0,0 +1,40 @@
+
+
+
+  
+  bug 1745638
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/navigation/test_bug1747019.html b/docshell/test/navigation/test_bug1747019.html
new file mode 100644
index 0000000000..c7995737df
--- /dev/null
+++ b/docshell/test/navigation/test_bug1747019.html
@@ -0,0 +1,48 @@
+
+
+
+  
+  Test session history and caching
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/navigation/test_bug1750973.html b/docshell/test/navigation/test_bug1750973.html
new file mode 100644
index 0000000000..9f87075b90
--- /dev/null
+++ b/docshell/test/navigation/test_bug1750973.html
@@ -0,0 +1,20 @@
+
+
+
+  
+  The layout state restoration when reframing the root element
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/navigation/test_bug1758664.html b/docshell/test/navigation/test_bug1758664.html
new file mode 100644
index 0000000000..662242e44a
--- /dev/null
+++ b/docshell/test/navigation/test_bug1758664.html
@@ -0,0 +1,21 @@
+
+
+
+  
+  Bug 1758664
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/navigation/test_bug270414.html b/docshell/test/navigation/test_bug270414.html
new file mode 100644
index 0000000000..0635e32888
--- /dev/null
+++ b/docshell/test/navigation/test_bug270414.html
@@ -0,0 +1,103 @@
+
+
+
+    
+    
+    
+    
+    
+
+
+
+Mozilla Bug 270414
+
+ + + + +
+
+
+
+ + diff --git a/docshell/test/navigation/test_bug278916.html b/docshell/test/navigation/test_bug278916.html new file mode 100644 index 0000000000..9e2335721e --- /dev/null +++ b/docshell/test/navigation/test_bug278916.html @@ -0,0 +1,37 @@ + + + + + + + + + + +Mozilla Bug 278916 + +
+
+
+ + diff --git a/docshell/test/navigation/test_bug279495.html b/docshell/test/navigation/test_bug279495.html new file mode 100644 index 0000000000..245ed14ed4 --- /dev/null +++ b/docshell/test/navigation/test_bug279495.html @@ -0,0 +1,44 @@ + + + + + + + + + + +Mozilla Bug 279495 + +
+
+
+ + diff --git a/docshell/test/navigation/test_bug344861.html b/docshell/test/navigation/test_bug344861.html new file mode 100644 index 0000000000..5ab8809d27 --- /dev/null +++ b/docshell/test/navigation/test_bug344861.html @@ -0,0 +1,35 @@ + + + + + Test for Bug 344861 + + + + +Mozilla Bug 344861 +

+ +
+
+
+ + + + diff --git a/docshell/test/navigation/test_bug386782.html b/docshell/test/navigation/test_bug386782.html new file mode 100644 index 0000000000..f6ad85f5a6 --- /dev/null +++ b/docshell/test/navigation/test_bug386782.html @@ -0,0 +1,122 @@ + + + + + Test for Bug 386782 + + + + + + + + +Mozilla Bug 386782 +

+ +
+
+
+ + diff --git a/docshell/test/navigation/test_bug430624.html b/docshell/test/navigation/test_bug430624.html new file mode 100644 index 0000000000..49afb023b9 --- /dev/null +++ b/docshell/test/navigation/test_bug430624.html @@ -0,0 +1,57 @@ + + + + + Test for Bug 430624 + + + + + +Mozilla Bug 430624 +

+ + + + +
+
+
+ + + + + + diff --git a/docshell/test/navigation/test_bug430723.html b/docshell/test/navigation/test_bug430723.html new file mode 100644 index 0000000000..dd85c7fb08 --- /dev/null +++ b/docshell/test/navigation/test_bug430723.html @@ -0,0 +1,124 @@ + + + + + Test for Bug 430723 + + + + + +Mozilla Bug 430723 +

+ +
+
+
+ + diff --git a/docshell/test/navigation/test_child.html b/docshell/test/navigation/test_child.html new file mode 100644 index 0000000000..87237471cd --- /dev/null +++ b/docshell/test/navigation/test_child.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +Mozilla Bug 408052 +
+ + + + +
+
+
+
+ + diff --git a/docshell/test/navigation/test_contentpolicy_block_window.html b/docshell/test/navigation/test_contentpolicy_block_window.html new file mode 100644 index 0000000000..7ce337c131 --- /dev/null +++ b/docshell/test/navigation/test_contentpolicy_block_window.html @@ -0,0 +1,98 @@ + + + + + Test for Bug 1329288 + + + + +Mozilla Bug 1329288 + + + +This is a link + + + + diff --git a/docshell/test/navigation/test_docshell_gotoindex.html b/docshell/test/navigation/test_docshell_gotoindex.html new file mode 100644 index 0000000000..992c9c9dbe --- /dev/null +++ b/docshell/test/navigation/test_docshell_gotoindex.html @@ -0,0 +1,29 @@ + + + + + Bug 1684310 + + + + + +

+ + diff --git a/docshell/test/navigation/test_dynamic_frame_forward_back.html b/docshell/test/navigation/test_dynamic_frame_forward_back.html new file mode 100644 index 0000000000..f3a349e09a --- /dev/null +++ b/docshell/test/navigation/test_dynamic_frame_forward_back.html @@ -0,0 +1,35 @@ + + + + + Test for Bug 508537 + + + + +Mozilla Bug 508537 +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_evict_from_bfcache.html b/docshell/test/navigation/test_evict_from_bfcache.html new file mode 100644 index 0000000000..0b1eb2fca4 --- /dev/null +++ b/docshell/test/navigation/test_evict_from_bfcache.html @@ -0,0 +1,63 @@ + + + + + Evict a page from bfcache + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_fragment_handling_during_load.html b/docshell/test/navigation/test_fragment_handling_during_load.html
new file mode 100644
index 0000000000..9c082c2ecf
--- /dev/null
+++ b/docshell/test/navigation/test_fragment_handling_during_load.html
@@ -0,0 +1,35 @@
+
+
+
+
+  Test for fragment navigation during load
+  
+  
+
+
+Mozilla Bug 978408
+

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_grandchild.html b/docshell/test/navigation/test_grandchild.html new file mode 100644 index 0000000000..10cf610664 --- /dev/null +++ b/docshell/test/navigation/test_grandchild.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +Mozilla Bug 408052 +
+ + + + +
+
+
+
+ + diff --git a/docshell/test/navigation/test_load_history_entry.html b/docshell/test/navigation/test_load_history_entry.html new file mode 100644 index 0000000000..8ca3fcb913 --- /dev/null +++ b/docshell/test/navigation/test_load_history_entry.html @@ -0,0 +1,196 @@ + + + + + + + + + + +Bug 1539482 +

+ +
+
+
+ + + + diff --git a/docshell/test/navigation/test_meta_refresh.html b/docshell/test/navigation/test_meta_refresh.html new file mode 100644 index 0000000000..bda9a9fe73 --- /dev/null +++ b/docshell/test/navigation/test_meta_refresh.html @@ -0,0 +1,42 @@ + + + + + Test meta refresh + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_navigation_type.html b/docshell/test/navigation/test_navigation_type.html
new file mode 100644
index 0000000000..75ea88bcbd
--- /dev/null
+++ b/docshell/test/navigation/test_navigation_type.html
@@ -0,0 +1,47 @@
+
+
+
+  
+  performance.navigation.type
+  
+  
+  
+
+
+

+ +

+
+
diff --git a/docshell/test/navigation/test_nested_frames.html b/docshell/test/navigation/test_nested_frames.html
new file mode 100644
index 0000000000..c3b49e0e23
--- /dev/null
+++ b/docshell/test/navigation/test_nested_frames.html
@@ -0,0 +1,35 @@
+
+
+
+
+  Test for Bug 1090918
+  
+  
+
+
+Mozilla Bug 1090918
+

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_new_shentry_during_history_navigation.html b/docshell/test/navigation/test_new_shentry_during_history_navigation.html new file mode 100644 index 0000000000..0c9adc5280 --- /dev/null +++ b/docshell/test/navigation/test_new_shentry_during_history_navigation.html @@ -0,0 +1,90 @@ + + + + + Test adding new session history entries while navigating to another one + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_not-opener.html b/docshell/test/navigation/test_not-opener.html
new file mode 100644
index 0000000000..acdb9473e6
--- /dev/null
+++ b/docshell/test/navigation/test_not-opener.html
@@ -0,0 +1,56 @@
+
+
+
+    
+    
+    
+    
+    
+
+
+
+Mozilla Bug 408052
+
+
+
+ + diff --git a/docshell/test/navigation/test_online_offline_bfcache.html b/docshell/test/navigation/test_online_offline_bfcache.html new file mode 100644 index 0000000000..4ad90fd52e --- /dev/null +++ b/docshell/test/navigation/test_online_offline_bfcache.html @@ -0,0 +1,101 @@ + + + + + Online/Offline with BFCache + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_open_javascript_noopener.html b/docshell/test/navigation/test_open_javascript_noopener.html
new file mode 100644
index 0000000000..81a6b70d61
--- /dev/null
+++ b/docshell/test/navigation/test_open_javascript_noopener.html
@@ -0,0 +1,44 @@
+
+
+
+  
+  
+  
+  
+  
+  
+  
+
diff --git a/docshell/test/navigation/test_opener.html b/docshell/test/navigation/test_opener.html
new file mode 100644
index 0000000000..ce966b897d
--- /dev/null
+++ b/docshell/test/navigation/test_opener.html
@@ -0,0 +1,56 @@
+
+
+
+    
+    
+    
+    
+    
+
+
+
+Mozilla Bug 408052
+
+
+
+ + diff --git a/docshell/test/navigation/test_performance_navigation.html b/docshell/test/navigation/test_performance_navigation.html new file mode 100644 index 0000000000..75abbdd767 --- /dev/null +++ b/docshell/test/navigation/test_performance_navigation.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 145971 + + + + +Mozilla Bug 145971 +

+ +
+
+
+ + diff --git a/docshell/test/navigation/test_popup-navigates-children.html b/docshell/test/navigation/test_popup-navigates-children.html new file mode 100644 index 0000000000..82d69e7982 --- /dev/null +++ b/docshell/test/navigation/test_popup-navigates-children.html @@ -0,0 +1,69 @@ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + diff --git a/docshell/test/navigation/test_rate_limit_location_change.html b/docshell/test/navigation/test_rate_limit_location_change.html new file mode 100644 index 0000000000..b1b51b92dd --- /dev/null +++ b/docshell/test/navigation/test_rate_limit_location_change.html @@ -0,0 +1,100 @@ + + + + + + Test for Bug 1314912 + + + + + +Mozilla Bug 1314912 +

+ +
+
+ + diff --git a/docshell/test/navigation/test_recursive_frames.html b/docshell/test/navigation/test_recursive_frames.html new file mode 100644 index 0000000000..3ccc09dd14 --- /dev/null +++ b/docshell/test/navigation/test_recursive_frames.html @@ -0,0 +1,167 @@ + + + + Test for Recursive Loads + + + + + +Mozilla Bug 1597427 +

+ +
+  
+
+
+ + + + + + +
+ + diff --git a/docshell/test/navigation/test_reload.html b/docshell/test/navigation/test_reload.html new file mode 100644 index 0000000000..7e75c7c035 --- /dev/null +++ b/docshell/test/navigation/test_reload.html @@ -0,0 +1,42 @@ + + + + + Ensure a page which is otherwise bfcacheable doesn't crash on reload + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_reload_large_postdata.html b/docshell/test/navigation/test_reload_large_postdata.html
new file mode 100644
index 0000000000..15fae33ac3
--- /dev/null
+++ b/docshell/test/navigation/test_reload_large_postdata.html
@@ -0,0 +1,61 @@
+
+
+
+  
+  
+
+
+

+ +
+ +
+ +
+
+
+ + diff --git a/docshell/test/navigation/test_reload_nonbfcached_srcdoc.html b/docshell/test/navigation/test_reload_nonbfcached_srcdoc.html new file mode 100644 index 0000000000..2399a0ad7d --- /dev/null +++ b/docshell/test/navigation/test_reload_nonbfcached_srcdoc.html @@ -0,0 +1,40 @@ + + + + + Test srcdoc handling when reloading a page. + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_reserved.html b/docshell/test/navigation/test_reserved.html
new file mode 100644
index 0000000000..0242f3941b
--- /dev/null
+++ b/docshell/test/navigation/test_reserved.html
@@ -0,0 +1,92 @@
+
+
+
+    
+    
+    
+    
+    
+
+
+
+Mozilla Bug 408052
+
+
+
+
+
+ + diff --git a/docshell/test/navigation/test_same_url.html b/docshell/test/navigation/test_same_url.html new file mode 100644 index 0000000000..820caa7005 --- /dev/null +++ b/docshell/test/navigation/test_same_url.html @@ -0,0 +1,56 @@ + + + + + + + + + + +Bug 1745730 +

+ +
+
+
+ + + + diff --git a/docshell/test/navigation/test_scrollRestoration.html b/docshell/test/navigation/test_scrollRestoration.html new file mode 100644 index 0000000000..d31598f391 --- /dev/null +++ b/docshell/test/navigation/test_scrollRestoration.html @@ -0,0 +1,214 @@ + + + + + Test for Bug 1155730 + + + + +Mozilla Bug 1155730 +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_session_history_entry_cleanup.html b/docshell/test/navigation/test_session_history_entry_cleanup.html new file mode 100644 index 0000000000..a55de0d6c3 --- /dev/null +++ b/docshell/test/navigation/test_session_history_entry_cleanup.html @@ -0,0 +1,35 @@ + + + + + Test for Bug 534178 + + + + +Mozilla Bug 534178 +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_session_history_on_redirect.html b/docshell/test/navigation/test_session_history_on_redirect.html new file mode 100644 index 0000000000..a303f81536 --- /dev/null +++ b/docshell/test/navigation/test_session_history_on_redirect.html @@ -0,0 +1,92 @@ + + + + + Session history on redirect + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_sessionhistory.html b/docshell/test/navigation/test_sessionhistory.html
new file mode 100644
index 0000000000..2254ec876b
--- /dev/null
+++ b/docshell/test/navigation/test_sessionhistory.html
@@ -0,0 +1,48 @@
+
+
+
+
+  Test for Bug 462076
+  
+  
+
+
+Mozilla Bug 462076
+

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_sessionhistory_document_write.html b/docshell/test/navigation/test_sessionhistory_document_write.html new file mode 100644 index 0000000000..2a48a8154e --- /dev/null +++ b/docshell/test/navigation/test_sessionhistory_document_write.html @@ -0,0 +1,34 @@ + + + + + Test for Session history + document.write + + + + +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_sessionhistory_iframe_removal.html b/docshell/test/navigation/test_sessionhistory_iframe_removal.html new file mode 100644 index 0000000000..242e3baade --- /dev/null +++ b/docshell/test/navigation/test_sessionhistory_iframe_removal.html @@ -0,0 +1,33 @@ + + + + + Test for Session history + document.write + + + + +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_shiftReload_and_pushState.html b/docshell/test/navigation/test_shiftReload_and_pushState.html new file mode 100644 index 0000000000..7525e2e21f --- /dev/null +++ b/docshell/test/navigation/test_shiftReload_and_pushState.html @@ -0,0 +1,35 @@ + + + + + Test for Bug 1003100 + + + + +Mozilla Bug 1003100 +

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_ship_beforeunload_fired.html b/docshell/test/navigation/test_ship_beforeunload_fired.html new file mode 100644 index 0000000000..e43711676b --- /dev/null +++ b/docshell/test/navigation/test_ship_beforeunload_fired.html @@ -0,0 +1,63 @@ + + + + Test that ensures beforeunload is fired when session-history-in-parent is enabled + + + + + + + diff --git a/docshell/test/navigation/test_ship_beforeunload_fired_2.html b/docshell/test/navigation/test_ship_beforeunload_fired_2.html new file mode 100644 index 0000000000..93669502a5 --- /dev/null +++ b/docshell/test/navigation/test_ship_beforeunload_fired_2.html @@ -0,0 +1,65 @@ + + + + Test that ensures beforeunload is fired when session-history-in-parent is enabled + + + + + + + diff --git a/docshell/test/navigation/test_ship_beforeunload_fired_3.html b/docshell/test/navigation/test_ship_beforeunload_fired_3.html new file mode 100644 index 0000000000..8951f269c5 --- /dev/null +++ b/docshell/test/navigation/test_ship_beforeunload_fired_3.html @@ -0,0 +1,65 @@ + + + + Test that ensures beforeunload is fired when session-history-in-parent is enabled + + + + + + + diff --git a/docshell/test/navigation/test_sibling-matching-parent.html b/docshell/test/navigation/test_sibling-matching-parent.html new file mode 100644 index 0000000000..3c1bc768db --- /dev/null +++ b/docshell/test/navigation/test_sibling-matching-parent.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +Mozilla Bug 408052 +
+ + + + +
+
+
+
+
+ + diff --git a/docshell/test/navigation/test_sibling-off-domain.html b/docshell/test/navigation/test_sibling-off-domain.html new file mode 100644 index 0000000000..cd70d1ae91 --- /dev/null +++ b/docshell/test/navigation/test_sibling-off-domain.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +Mozilla Bug 408052 +
+ + + + +
+
+
+
+
+ + diff --git a/docshell/test/navigation/test_state_size.html b/docshell/test/navigation/test_state_size.html new file mode 100644 index 0000000000..f089a460ec --- /dev/null +++ b/docshell/test/navigation/test_state_size.html @@ -0,0 +1,32 @@ + + + + + Test the max size of the data parameter of push/replaceState + + + + + +

+ +

+
+
diff --git a/docshell/test/navigation/test_static_and_dynamic.html b/docshell/test/navigation/test_static_and_dynamic.html
new file mode 100644
index 0000000000..ff72a8188c
--- /dev/null
+++ b/docshell/test/navigation/test_static_and_dynamic.html
@@ -0,0 +1,36 @@
+
+
+
+
+  Test for static and dynamic frames and forward-back 
+  
+  
+
+
+Mozilla Bug 
+

+ +
+  
+
+ + diff --git a/docshell/test/navigation/test_triggeringprincipal_frame_nav.html b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html new file mode 100644 index 0000000000..d7046e9236 --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html @@ -0,0 +1,74 @@ + + + + + Bug 1181370 - Test triggeringPrincipal for iframe navigations + + + + + + + + + + + diff --git a/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html b/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html new file mode 100644 index 0000000000..4483efb13e --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html @@ -0,0 +1,63 @@ + + + + + Bug 1639195 - Test triggeringPrincipal for iframe same-origin navigations + + + + + + + + + + + diff --git a/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html b/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html new file mode 100644 index 0000000000..115c5f4462 --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + +
+ + diff --git a/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html b/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html new file mode 100644 index 0000000000..1611ebf479 --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + diff --git a/docshell/test/navigation/test_triggeringprincipal_window_open.html b/docshell/test/navigation/test_triggeringprincipal_window_open.html new file mode 100644 index 0000000000..439a125f97 --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_window_open.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + diff --git a/docshell/test/unit/AllowJavascriptChild.sys.mjs b/docshell/test/unit/AllowJavascriptChild.sys.mjs new file mode 100644 index 0000000000..a7c3fa6172 --- /dev/null +++ b/docshell/test/unit/AllowJavascriptChild.sys.mjs @@ -0,0 +1,41 @@ +export class AllowJavascriptChild extends JSWindowActorChild { + async receiveMessage(msg) { + switch (msg.name) { + case "CheckScriptsAllowed": + return this.checkScriptsAllowed(); + case "CheckFiredLoadEvent": + return this.contentWindow.wrappedJSObject.gFiredOnload; + case "CreateIframe": + return this.createIframe(msg.data.url); + } + return null; + } + + handleEvent(event) { + if (event.type === "load") { + this.sendAsyncMessage("LoadFired"); + } + } + + checkScriptsAllowed() { + let win = this.contentWindow; + + win.wrappedJSObject.gFiredOnclick = false; + win.document.body.click(); + return win.wrappedJSObject.gFiredOnclick; + } + + async createIframe(url) { + let doc = this.contentWindow.document; + + let iframe = doc.createElement("iframe"); + iframe.src = url; + doc.body.appendChild(iframe); + + await new Promise(resolve => { + iframe.addEventListener("load", resolve, { once: true }); + }); + + return iframe.browsingContext; + } +} diff --git a/docshell/test/unit/AllowJavascriptParent.sys.mjs b/docshell/test/unit/AllowJavascriptParent.sys.mjs new file mode 100644 index 0000000000..5631fcdb09 --- /dev/null +++ b/docshell/test/unit/AllowJavascriptParent.sys.mjs @@ -0,0 +1,28 @@ +let loadPromises = new WeakMap(); + +export class AllowJavascriptParent extends JSWindowActorParent { + async receiveMessage(msg) { + switch (msg.name) { + case "LoadFired": + let bc = this.browsingContext; + let deferred = loadPromises.get(bc); + if (deferred) { + loadPromises.delete(bc); + deferred.resolve(this); + } + break; + } + } + + static promiseLoad(bc) { + let deferred = loadPromises.get(bc); + if (!deferred) { + deferred = {}; + deferred.promise = new Promise(resolve => { + deferred.resolve = resolve; + }); + loadPromises.set(bc, deferred); + } + return deferred.promise; + } +} diff --git a/docshell/test/unit/data/engine.xml b/docshell/test/unit/data/engine.xml new file mode 100644 index 0000000000..3a2bd85c1b --- /dev/null +++ b/docshell/test/unit/data/engine.xml @@ -0,0 +1,10 @@ + + +test_urifixup_search_engine +test_urifixup_search_engine +UTF-8 + + + +https://www.example.org/ + diff --git a/docshell/test/unit/data/enginePost.xml b/docshell/test/unit/data/enginePost.xml new file mode 100644 index 0000000000..14775b6f0a --- /dev/null +++ b/docshell/test/unit/data/enginePost.xml @@ -0,0 +1,10 @@ + + +test_urifixup_search_engine_post +test_urifixup_search_engine_post +UTF-8 + + + +https://www.example.org/ + diff --git a/docshell/test/unit/data/enginePrivate.xml b/docshell/test/unit/data/enginePrivate.xml new file mode 100644 index 0000000000..7d87de98fa --- /dev/null +++ b/docshell/test/unit/data/enginePrivate.xml @@ -0,0 +1,10 @@ + + +test_urifixup_search_engine_private +test_urifixup_search_engine_private +UTF-8 + + + +https://www.example.org/ + diff --git a/docshell/test/unit/head_docshell.js b/docshell/test/unit/head_docshell.js new file mode 100644 index 0000000000..5b0369089e --- /dev/null +++ b/docshell/test/unit/head_docshell.js @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +ChromeUtils.defineESModuleGetters(this, { + AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs", + SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs", + SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", + TestUtils: "resource://testing-common/TestUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(this, { + HttpServer: "resource://testing-common/httpd.js", + NetUtil: "resource://gre/modules/NetUtil.jsm", +}); + +var profileDir = do_get_profile(); + +const kSearchEngineID = "test_urifixup_search_engine"; +const kSearchEngineURL = "https://www.example.org/?search={searchTerms}"; +const kPrivateSearchEngineID = "test_urifixup_search_engine_private"; +const kPrivateSearchEngineURL = + "https://www.example.org/?private={searchTerms}"; +const kPostSearchEngineID = "test_urifixup_search_engine_post"; +const kPostSearchEngineURL = "https://www.example.org/"; +const kPostSearchEngineData = "q={searchTerms}"; + +const SEARCH_CONFIG = [ + { + appliesTo: [ + { + included: { + everywhere: true, + }, + }, + ], + default: "yes", + webExtension: { + id: "fixup_search@search.mozilla.org", + }, + }, +]; + +async function setupSearchService() { + SearchTestUtils.init(this); + + AddonTestUtils.init(this); + AddonTestUtils.overrideCertDB(); + AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "42" + ); + + await SearchTestUtils.useTestEngines(".", null, SEARCH_CONFIG); + await AddonTestUtils.promiseStartupManager(); + await Services.search.init(); +} + +/** + * After useHttpServer() is called, this string contains the URL of the "data" + * directory, including the final slash. + */ +var gDataUrl; + +/** + * Initializes the HTTP server and ensures that it is terminated when tests end. + * + * @param {string} dir + * The test sub-directory to use for the engines. + * @returns {HttpServer} + * The HttpServer object in case further customization is needed. + */ +function useHttpServer(dir = "data") { + let httpServer = new HttpServer(); + httpServer.start(-1); + httpServer.registerDirectory("/", do_get_cwd()); + gDataUrl = `http://localhost:${httpServer.identity.primaryPort}/${dir}/`; + registerCleanupFunction(async function cleanup_httpServer() { + await new Promise(resolve => { + httpServer.stop(resolve); + }); + }); + return httpServer; +} + +async function addTestEngines() { + useHttpServer(); + // This is a hack, ideally we should be setting up a configuration with + // built-in engines, but the `chrome_settings_overrides` section that + // WebExtensions need is only defined for browser/ + await SearchTestUtils.promiseNewSearchEngine({ + url: `${gDataUrl}/engine.xml`, + }); + await SearchTestUtils.promiseNewSearchEngine({ + url: `${gDataUrl}/enginePrivate.xml`, + }); + await SearchTestUtils.promiseNewSearchEngine({ + url: `${gDataUrl}/enginePost.xml`, + }); +} diff --git a/docshell/test/unit/test_URIFixup.js b/docshell/test/unit/test_URIFixup.js new file mode 100644 index 0000000000..7967933b56 --- /dev/null +++ b/docshell/test/unit/test_URIFixup.js @@ -0,0 +1,123 @@ +var pref = "browser.fixup.typo.scheme"; + +var data = [ + { + // ttp -> http. + wrong: "ttp://www.example.com/", + fixed: "http://www.example.com/", + }, + { + // htp -> http. + wrong: "htp://www.example.com/", + fixed: "http://www.example.com/", + }, + { + // ttps -> https. + wrong: "ttps://www.example.com/", + fixed: "https://www.example.com/", + }, + { + // tps -> https. + wrong: "tps://www.example.com/", + fixed: "https://www.example.com/", + }, + { + // ps -> https. + wrong: "ps://www.example.com/", + fixed: "https://www.example.com/", + }, + { + // htps -> https. + wrong: "htps://www.example.com/", + fixed: "https://www.example.com/", + }, + { + // ile -> file. + wrong: "ile:///this/is/a/test.html", + fixed: "file:///this/is/a/test.html", + }, + { + // le -> file. + wrong: "le:///this/is/a/test.html", + fixed: "file:///this/is/a/test.html", + }, + { + // Replace ';' with ':'. + wrong: "http;//www.example.com/", + fixed: "http://www.example.com/", + noPrefValue: "http://http;//www.example.com/", + }, + { + // Missing ':'. + wrong: "https//www.example.com/", + fixed: "https://www.example.com/", + noPrefValue: "http://https//www.example.com/", + }, + { + // Missing ':' for file scheme. + wrong: "file///this/is/a/test.html", + fixed: "file:///this/is/a/test.html", + noPrefValue: "http://file///this/is/a/test.html", + }, + { + // Valid should not be changed. + wrong: "https://example.com/this/is/a/test.html", + fixed: "https://example.com/this/is/a/test.html", + }, + { + // Unmatched should not be changed. + wrong: "whatever://this/is/a/test.html", + fixed: "whatever://this/is/a/test.html", + }, +]; + +var len = data.length; + +add_task(async function setup() { + await setupSearchService(); + // Now we've initialised the search service, we force remove the engines + // it has, so they don't interfere with this test. + // Search engine integration is tested in test_URIFixup_search.js. + Services.search.wrappedJSObject._engines.clear(); +}); + +// Make sure we fix what needs fixing when there is no pref set. +add_task(function test_unset_pref_fixes_typos() { + Services.prefs.clearUserPref(pref); + for (let i = 0; i < len; ++i) { + let item = data[i]; + let { preferredURI } = Services.uriFixup.getFixupURIInfo( + item.wrong, + Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS + ); + Assert.equal(preferredURI.spec, item.fixed); + } +}); + +// Make sure we don't do anything when the pref is explicitly +// set to false. +add_task(function test_false_pref_keeps_typos() { + Services.prefs.setBoolPref(pref, false); + for (let i = 0; i < len; ++i) { + let item = data[i]; + let { preferredURI } = Services.uriFixup.getFixupURIInfo( + item.wrong, + Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS + ); + Assert.equal(preferredURI.spec, item.noPrefValue || item.wrong); + } +}); + +// Finally, make sure we still fix what needs fixing if the pref is +// explicitly set to true. +add_task(function test_true_pref_fixes_typos() { + Services.prefs.setBoolPref(pref, true); + for (let i = 0; i < len; ++i) { + let item = data[i]; + let { preferredURI } = Services.uriFixup.getFixupURIInfo( + item.wrong, + Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS + ); + Assert.equal(preferredURI.spec, item.fixed); + } +}); diff --git a/docshell/test/unit/test_URIFixup_check_host.js b/docshell/test/unit/test_URIFixup_check_host.js new file mode 100644 index 0000000000..132d74ca9b --- /dev/null +++ b/docshell/test/unit/test_URIFixup_check_host.js @@ -0,0 +1,183 @@ +/* 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 lazy = {}; + +// Ensure DNS lookups don't hit the server +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gDNSOverride", + "@mozilla.org/network/native-dns-override;1", + "nsINativeDNSResolverOverride" +); + +add_task(async function setup() { + Services.prefs.setStringPref("browser.fixup.alternate.prefix", "www."); + Services.prefs.setStringPref("browser.fixup.alternate.suffix", ".com"); + Services.prefs.setStringPref("browser.fixup.alternate.protocol", "https"); + Services.prefs.setBoolPref( + "browser.urlbar.dnsResolveFullyQualifiedNames", + true + ); + registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.fixup.alternate.prefix"); + Services.prefs.clearUserPref("browser.fixup.alternate.suffix"); + Services.prefs.clearUserPref("browser.fixup.alternate.protocol"); + Services.prefs.clearUserPref( + "browser.urlbar.dnsResolveFullyQualifiedNames" + ); + }); +}); + +// TODO: Export Listener from test_dns_override instead of duping +class Listener { + constructor() { + this.promise = new Promise(resolve => { + this.resolve = resolve; + }); + } + + onLookupComplete(inRequest, inRecord, inStatus) { + this.resolve([inRequest, inRecord, inStatus]); + } + + async firstAddress() { + let all = await this.addresses(); + if (all.length) { + return all[0]; + } + return undefined; + } + + async addresses() { + let [, inRecord] = await this.promise; + let addresses = []; + if (!inRecord) { + return addresses; // returns [] + } + inRecord.QueryInterface(Ci.nsIDNSAddrRecord); + while (inRecord.hasMore()) { + addresses.push(inRecord.getNextAddrAsString()); + } + return addresses; + } + + then() { + return this.promise.then.apply(this.promise, arguments); + } +} + +const FAKE_IP = "::1"; +const FAKE_INTRANET_IP = "::2"; +const ORIGIN_ATTRIBUTE = {}; + +add_task(async function test_uri_with_force_fixup() { + let listener = new Listener(); + let { fixedURI } = Services.uriFixup.forceHttpFixup("http://www.example.com"); + + lazy.gDNSOverride.addIPOverride(fixedURI.displayHost, FAKE_IP); + + Services.uriFixup.checkHost(fixedURI, listener, ORIGIN_ATTRIBUTE); + Assert.equal( + await listener.firstAddress(), + FAKE_IP, + "Should've received fake IP" + ); + + lazy.gDNSOverride.clearHostOverride(fixedURI.displayHost); + Services.dns.clearCache(false); +}); + +add_task(async function test_uri_with_get_fixup() { + let listener = new Listener(); + let uri = Services.io.newURI("http://www.example.com"); + + lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP); + + Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE); + Assert.equal( + await listener.firstAddress(), + FAKE_IP, + "Should've received fake IP" + ); + + lazy.gDNSOverride.clearHostOverride(uri.displayHost); + Services.dns.clearCache(false); +}); + +add_task(async function test_intranet_like_uri() { + let listener = new Listener(); + let uri = Services.io.newURI("http://someintranet"); + + lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP); + // Hosts without periods should end with a period to make them FQN + lazy.gDNSOverride.addIPOverride(uri.displayHost + ".", FAKE_INTRANET_IP); + + Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE); + Assert.deepEqual( + await listener.addresses(), + FAKE_INTRANET_IP, + "Should've received fake intranet IP" + ); + + lazy.gDNSOverride.clearHostOverride(uri.displayHost); + lazy.gDNSOverride.clearHostOverride(uri.displayHost + "."); + Services.dns.clearCache(false); +}); + +add_task(async function test_intranet_like_uri_without_fixup() { + let listener = new Listener(); + let uri = Services.io.newURI("http://someintranet"); + Services.prefs.setBoolPref( + "browser.urlbar.dnsResolveFullyQualifiedNames", + false + ); + + lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP); + // Hosts without periods should end with a period to make them FQN + lazy.gDNSOverride.addIPOverride(uri.displayHost + ".", FAKE_INTRANET_IP); + + Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE); + Assert.deepEqual( + await listener.addresses(), + FAKE_IP, + "Should've received non-fixed up IP" + ); + + lazy.gDNSOverride.clearHostOverride(uri.displayHost); + lazy.gDNSOverride.clearHostOverride(uri.displayHost + "."); + Services.dns.clearCache(false); +}); + +add_task(async function test_ip_address() { + let listener = new Listener(); + let uri = Services.io.newURI("http://192.168.0.1"); + + // Testing IP address being passed into the method + // requires observing if the asyncResolve method gets called + let didResolve = false; + let topic = "uri-fixup-check-dns"; + let observer = { + observe(aSubject, aTopicInner, aData) { + if (aTopicInner == topic) { + didResolve = true; + } + }, + }; + Services.obs.addObserver(observer, topic); + lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP); + + Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE); + Assert.equal( + didResolve, + false, + "Passing an IP address should not conduct a host lookup" + ); + + lazy.gDNSOverride.clearHostOverride(uri.displayHost); + Services.dns.clearCache(false); + Services.obs.removeObserver(observer, topic); +}); diff --git a/docshell/test/unit/test_URIFixup_external_protocol_fallback.js b/docshell/test/unit/test_URIFixup_external_protocol_fallback.js new file mode 100644 index 0000000000..0846070b7a --- /dev/null +++ b/docshell/test/unit/test_URIFixup_external_protocol_fallback.js @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Test whether fallback mechanism is working if don't trust nsIExternalProtocolService. + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); + +add_task(async function setup() { + info( + "Prepare mock nsIExternalProtocolService whose externalProtocolHandlerExists returns always true" + ); + const externalProtocolService = Cc[ + "@mozilla.org/uriloader/external-protocol-service;1" + ].getService(Ci.nsIExternalProtocolService); + const mockId = MockRegistrar.register( + "@mozilla.org/uriloader/external-protocol-service;1", + { + getProtocolHandlerInfo: scheme => + externalProtocolService.getProtocolHandlerInfo(scheme), + externalProtocolHandlerExists: () => true, + QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]), + } + ); + const mockExternalProtocolService = Cc[ + "@mozilla.org/uriloader/external-protocol-service;1" + ].getService(Ci.nsIExternalProtocolService); + Assert.ok( + mockExternalProtocolService.externalProtocolHandlerExists("__invalid__"), + "Mock service is working" + ); + + info("Register new dummy protocol"); + const dummyProtocolHandlerInfo = + externalProtocolService.getProtocolHandlerInfo("dummy"); + const handlerService = Cc[ + "@mozilla.org/uriloader/handler-service;1" + ].getService(Ci.nsIHandlerService); + handlerService.store(dummyProtocolHandlerInfo); + + info("Prepare test search engine"); + await setupSearchService(); + await addTestEngines(); + await Services.search.setDefault( + Services.search.getEngineByName(kSearchEngineID), + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + + registerCleanupFunction(() => { + handlerService.remove(dummyProtocolHandlerInfo); + MockRegistrar.unregister(mockId); + }); +}); + +add_task(function basic() { + const testData = [ + { + input: "mailto:test@example.com", + expected: + isSupportedInHandlerService("mailto") || + // Thunderbird IS a mailto handler, it doesn't have handlers. + AppConstants.MOZ_APP_NAME == "thunderbird" + ? "mailto:test@example.com" + : "http://mailto:test@example.com/", + }, + { + input: "keyword:search", + expected: "https://www.example.org/?search=keyword%3Asearch", + }, + { + input: "dummy:protocol", + expected: "dummy:protocol", + }, + ]; + + for (const { input, expected } of testData) { + assertFixup(input, expected); + } +}); + +function assertFixup(input, expected) { + const { preferredURI } = Services.uriFixup.getFixupURIInfo( + input, + Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS + ); + Assert.equal(preferredURI.spec, expected); +} + +function isSupportedInHandlerService(scheme) { + const externalProtocolService = Cc[ + "@mozilla.org/uriloader/external-protocol-service;1" + ].getService(Ci.nsIExternalProtocolService); + const handlerService = Cc[ + "@mozilla.org/uriloader/handler-service;1" + ].getService(Ci.nsIHandlerService); + return handlerService.exists( + externalProtocolService.getProtocolHandlerInfo(scheme) + ); +} diff --git a/docshell/test/unit/test_URIFixup_forced.js b/docshell/test/unit/test_URIFixup_forced.js new file mode 100644 index 0000000000..1d4dfd94a7 --- /dev/null +++ b/docshell/test/unit/test_URIFixup_forced.js @@ -0,0 +1,159 @@ +/* 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 data = [ + { + // singleword. + wrong: "http://example/", + fixed: "https://www.example.com/", + }, + { + // upgrade protocol. + wrong: "http://www.example.com/", + fixed: "https://www.example.com/", + noAlternateURI: true, + }, + { + // no difference. + wrong: "https://www.example.com/", + fixed: "https://www.example.com/", + noProtocolFixup: true, + noAlternateURI: true, + }, + { + // don't add prefix when there's more than one dot. + wrong: "http://www.example.abc.def/", + fixed: "https://www.example.abc.def/", + noAlternateURI: true, + }, + { + // http -> https. + wrong: "http://www.example/", + fixed: "https://www.example/", + noAlternateURI: true, + }, + { + // domain.com -> https://www.domain.com. + wrong: "http://example.com/", + fixed: "https://www.example.com/", + }, + { + // example/example... -> https://www.example.com/example/ + wrong: "http://example/example/", + fixed: "https://www.example.com/example/", + }, + { + // example/example/s#q -> www.example.com/example/s#q. + wrong: "http://example/example/s#q", + fixed: "https://www.example.com/example/s#q", + }, + { + wrong: "http://モジラ.org", + fixed: "https://www.xn--yck6dwa.org/", + }, + { + wrong: "http://モジラ", + fixed: "https://www.xn--yck6dwa.com/", + }, + { + wrong: "http://xn--yck6dwa", + fixed: "https://www.xn--yck6dwa.com/", + }, + { + wrong: "https://モジラ.org", + fixed: "https://www.xn--yck6dwa.org/", + noProtocolFixup: true, + }, + { + wrong: "https://モジラ", + fixed: "https://www.xn--yck6dwa.com/", + noProtocolFixup: true, + }, + { + wrong: "https://xn--yck6dwa", + fixed: "https://www.xn--yck6dwa.com/", + noProtocolFixup: true, + }, + { + // Host is https -> fixup typos of protocol + wrong: "htp://https://mozilla.org", + fixed: "http://https//mozilla.org", + noAlternateURI: true, + }, + { + // Host is http -> fixup typos of protocol + wrong: "ttp://http://mozilla.org", + fixed: "http://http//mozilla.org", + noAlternateURI: true, + }, + { + // Host is localhost -> fixup typos of protocol + wrong: "htps://localhost://mozilla.org", + fixed: "https://localhost//mozilla.org", + noAlternateURI: true, + }, + { + // view-source is not http/https -> error + wrong: "view-source:http://example/example/example/example", + reject: true, + comment: "Scheme should be either http or https", + }, + { + // file is not http/https -> error + wrong: "file://http://example/example/example/example", + reject: true, + comment: "Scheme should be either http or https", + }, + { + // Protocol is missing -> error + wrong: "example.com", + reject: true, + comment: "Scheme should be either http or https", + }, + { + // Empty input -> error + wrong: "", + reject: true, + comment: "Should pass a non-null uri", + }, +]; + +add_task(async function setup() { + Services.prefs.setStringPref("browser.fixup.alternate.prefix", "www."); + Services.prefs.setStringPref("browser.fixup.alternate.suffix", ".com"); + Services.prefs.setStringPref("browser.fixup.alternate.protocol", "https"); + registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.fixup.alternate.prefix"); + Services.prefs.clearUserPref("browser.fixup.alternate.suffix"); + Services.prefs.clearUserPref("browser.fixup.alternate.protocol"); + }); +}); + +add_task(function test_default_https_pref() { + for (let item of data) { + if (item.reject) { + Assert.throws( + () => Services.uriFixup.forceHttpFixup(item.wrong), + /NS_ERROR_FAILURE/, + item.comment + ); + } else { + let { fixupChangedProtocol, fixupCreatedAlternateURI, fixedURI } = + Services.uriFixup.forceHttpFixup(item.wrong); + Assert.equal(fixedURI.spec, item.fixed, "Specs should be the same"); + Assert.equal( + fixupChangedProtocol, + !item.noProtocolFixup, + `fixupChangedProtocol should be ${!item.noAlternateURI}` + ); + Assert.equal( + fixupCreatedAlternateURI, + !item.noAlternateURI, + `fixupCreatedAlternateURI should be ${!item.limitedFixup}` + ); + } + } +}); diff --git a/docshell/test/unit/test_URIFixup_info.js b/docshell/test/unit/test_URIFixup_info.js new file mode 100644 index 0000000000..89514cb00c --- /dev/null +++ b/docshell/test/unit/test_URIFixup_info.js @@ -0,0 +1,1075 @@ +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const kForceDNSLookup = "browser.fixup.dns_first_for_single_words"; +const kFixupEnabled = "browser.fixup.alternate.enabled"; + +// TODO(bug 1522134), this test should also use +// combinations of the following flags. +var flagInputs = [ + Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP | + Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT, + Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI, + Services.uriFixup.FIXUP_FLAG_FORCE_ALTERNATE_URI, + Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS, + // This should not really generate a search, but it does, see Bug 1588118. + Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS | + Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT, +]; + +/* + The following properties are supported for these test cases: + { + input: "", // Input string, required + fixedURI: "", // Expected fixedURI + alternateURI: "", // Expected alternateURI + keywordLookup: false, // Whether a keyword lookup is expected + protocolChange: false, // Whether a protocol change is expected + inWhitelist: false, // Whether the input host is in the whitelist + affectedByDNSForSingleWordHosts: false, // Whether the input host could be a host, but is normally assumed to be a keyword query + } +*/ +var testcases = [ + { + input: "about:home", + fixedURI: "about:home", + }, + { + input: "http://www.mozilla.org", + fixedURI: "http://www.mozilla.org/", + }, + { + input: "http://127.0.0.1/", + fixedURI: "http://127.0.0.1/", + }, + { + input: "file:///foo/bar", + fixedURI: "file:///foo/bar", + }, + { + input: "://www.mozilla.org", + fixedURI: "http://www.mozilla.org/", + protocolChange: true, + }, + { + input: "www.mozilla.org", + fixedURI: "http://www.mozilla.org/", + protocolChange: true, + }, + { + input: "http://mozilla/", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + }, + { + input: "http://test./", + fixedURI: "http://test./", + alternateURI: "https://www.test./", + }, + { + input: "127.0.0.1", + fixedURI: "http://127.0.0.1/", + protocolChange: true, + }, + { + input: "1.2.3.4/", + fixedURI: "http://1.2.3.4/", + protocolChange: true, + }, + { + input: "1.2.3.4/foo", + fixedURI: "http://1.2.3.4/foo", + protocolChange: true, + }, + { + input: "1.2.3.4:8000", + fixedURI: "http://1.2.3.4:8000/", + protocolChange: true, + }, + { + input: "1.2.3.4:8000/", + fixedURI: "http://1.2.3.4:8000/", + protocolChange: true, + }, + { + input: "1.2.3.4:8000/foo", + fixedURI: "http://1.2.3.4:8000/foo", + protocolChange: true, + }, + { + input: "192.168.10.110", + fixedURI: "http://192.168.10.110/", + protocolChange: true, + }, + { + input: "192.168.10.110/123", + fixedURI: "http://192.168.10.110/123", + protocolChange: true, + }, + { + input: "192.168.10.110/123foo", + fixedURI: "http://192.168.10.110/123foo", + protocolChange: true, + }, + { + input: "192.168.10.110:1234/123", + fixedURI: "http://192.168.10.110:1234/123", + protocolChange: true, + }, + { + input: "192.168.10.110:1234/123foo", + fixedURI: "http://192.168.10.110:1234/123foo", + protocolChange: true, + }, + { + input: "1.2.3", + fixedURI: "http://1.2.0.3/", + protocolChange: true, + }, + { + input: "1.2.3/", + fixedURI: "http://1.2.0.3/", + protocolChange: true, + }, + { + input: "1.2.3/foo", + fixedURI: "http://1.2.0.3/foo", + protocolChange: true, + }, + { + input: "1.2.3/123", + fixedURI: "http://1.2.0.3/123", + protocolChange: true, + }, + { + input: "1.2.3:8000", + fixedURI: "http://1.2.0.3:8000/", + protocolChange: true, + }, + { + input: "1.2.3:8000/", + fixedURI: "http://1.2.0.3:8000/", + protocolChange: true, + }, + { + input: "1.2.3:8000/foo", + fixedURI: "http://1.2.0.3:8000/foo", + protocolChange: true, + }, + { + input: "1.2.3:8000/123", + fixedURI: "http://1.2.0.3:8000/123", + protocolChange: true, + }, + { + input: "http://1.2.3", + fixedURI: "http://1.2.0.3/", + }, + { + input: "http://1.2.3/", + fixedURI: "http://1.2.0.3/", + }, + { + input: "http://1.2.3/foo", + fixedURI: "http://1.2.0.3/foo", + }, + { + input: "[::1]", + fixedURI: "http://[::1]/", + protocolChange: true, + }, + { + input: "[::1]/", + fixedURI: "http://[::1]/", + protocolChange: true, + }, + { + input: "[::1]:8000", + fixedURI: "http://[::1]:8000/", + protocolChange: true, + }, + { + input: "[::1]:8000/", + fixedURI: "http://[::1]:8000/", + protocolChange: true, + }, + { + input: "[[::1]]/", + keywordLookup: true, + }, + { + input: "[fe80:cd00:0:cde:1257:0:211e:729c]", + fixedURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/", + protocolChange: true, + }, + { + input: "[64:ff9b::8.8.8.8]", + fixedURI: "http://[64:ff9b::808:808]/", + protocolChange: true, + }, + { + input: "[64:ff9b::8.8.8.8]/~moz", + fixedURI: "http://[64:ff9b::808:808]/~moz", + protocolChange: true, + }, + { + input: "[::1][::1]", + keywordLookup: true, + }, + { + input: "[::1][100", + keywordLookup: true, + }, + { + input: "[::1]]", + keywordLookup: true, + }, + { + input: "1234", + fixedURI: "http://0.0.4.210/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "whitelisted/foo.txt", + fixedURI: "http://whitelisted/foo.txt", + alternateURI: "https://www.whitelisted.com/foo.txt", + protocolChange: true, + }, + { + input: "mozilla", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "test.", + fixedURI: "http://test./", + alternateURI: "https://www.test./", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: ".test", + fixedURI: "http://.test/", + alternateURI: "https://www.test/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mozilla is amazing", + keywordLookup: true, + }, + { + input: "search ?mozilla", + keywordLookup: true, + }, + { + input: "mozilla .com", + keywordLookup: true, + }, + { + input: "what if firefox?", + keywordLookup: true, + }, + { + input: "london's map", + keywordLookup: true, + }, + { + input: "mozilla ", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: " mozilla ", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mozilla \\", + keywordLookup: true, + }, + { + input: "mozilla \\ foo.txt", + keywordLookup: true, + }, + { + input: "mozilla \\\r foo.txt", + keywordLookup: true, + }, + { + input: "mozilla\n", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mozilla \r\n", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "moz\r\nfirefox\nos\r", + fixedURI: "http://mozfirefoxos/", + alternateURI: "https://www.mozfirefoxos.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "moz\r\n firefox\n", + keywordLookup: true, + }, + { + input: "", + keywordLookup: true, + }, + { + input: "[]", + keywordLookup: true, + }, + { + input: "http://whitelisted/", + fixedURI: "http://whitelisted/", + alternateURI: "https://www.whitelisted.com/", + inWhitelist: true, + }, + { + input: "whitelisted", + fixedURI: "http://whitelisted/", + alternateURI: "https://www.whitelisted.com/", + protocolChange: true, + inWhitelist: true, + }, + { + input: "whitelisted.", + fixedURI: "http://whitelisted./", + alternateURI: "https://www.whitelisted./", + protocolChange: true, + inWhitelist: true, + }, + { + input: "mochi.test", + fixedURI: "http://mochi.test/", + alternateURI: "https://www.mochi.test/", + protocolChange: true, + inWhitelist: true, + }, + // local.domain is a whitelisted suffix... + { + input: "some.local.domain", + fixedURI: "http://some.local.domain/", + protocolChange: true, + inWhitelist: true, + }, + // ...but .domain is not. + { + input: "some.domain", + fixedURI: "http://some.domain/", + alternateURI: "https://www.some.domain/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "café.com", + fixedURI: "http://xn--caf-dma.com/", + alternateURI: "https://www.xn--caf-dma.com/", + protocolChange: true, + }, + { + input: "mozilla.nonexistent", + fixedURI: "http://mozilla.nonexistent/", + alternateURI: "https://www.mozilla.nonexistent/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mochi.ocm", + fixedURI: "http://mochi.com/", + alternateURI: "https://www.mochi.com/", + protocolChange: true, + }, + { + input: "47.6182,-122.830", + fixedURI: "http://47.6182,-122.830/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "-47.6182,-23.51", + fixedURI: "http://-47.6182,-23.51/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "-22.14,23.51-", + fixedURI: "http://-22.14,23.51-/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "32.7", + fixedURI: "http://32.0.0.7/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "5+2", + fixedURI: "http://5+2/", + alternateURI: "https://www.5+2.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "5/2", + fixedURI: "http://0.0.0.5/2", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "moz ?.::%27", + keywordLookup: true, + }, + { + input: "mozilla.com/?q=search", + fixedURI: "http://mozilla.com/?q=search", + alternateURI: "https://www.mozilla.com/?q=search", + protocolChange: true, + }, + { + input: "mozilla.com?q=search", + fixedURI: "http://mozilla.com/?q=search", + alternateURI: "https://www.mozilla.com/?q=search", + protocolChange: true, + }, + { + input: "mozilla.com ?q=search", + keywordLookup: true, + }, + { + input: "mozilla.com.?q=search", + fixedURI: "http://mozilla.com./?q=search", + protocolChange: true, + }, + { + input: "mozilla.com'?q=search", + fixedURI: "http://mozilla.com/?q=search", + alternateURI: "https://www.mozilla.com/?q=search", + protocolChange: true, + }, + { + input: "mozilla.com':search", + keywordLookup: true, + }, + { + input: "[mozilla]", + keywordLookup: true, + }, + { + input: "':?", + fixedURI: "http://'/?", + alternateURI: "https://www.'.com/?", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "whitelisted?.com", + fixedURI: "http://whitelisted/?.com", + alternateURI: "https://www.whitelisted.com/?.com", + protocolChange: true, + }, + { + input: "?'.com", + keywordLookup: true, + }, + { + input: ".com", + keywordLookup: true, + affectedByDNSForSingleWordHosts: true, + fixedURI: "http://.com/", + alternateURI: "https://www.com/", + protocolChange: true, + }, + { + input: "' ?.com", + keywordLookup: true, + }, + { + input: "?mozilla", + keywordLookup: true, + }, + { + input: "??mozilla", + keywordLookup: true, + }, + { + input: "mozilla/", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + protocolChange: true, + }, + { + input: "mozilla", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + protocolChange: true, + keywordLookup: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mozilla5/2", + fixedURI: "http://mozilla5/2", + alternateURI: "https://www.mozilla5.com/2", + protocolChange: true, + keywordLookup: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mozilla/foo", + fixedURI: "http://mozilla/foo", + alternateURI: "https://www.mozilla.com/foo", + protocolChange: true, + keywordLookup: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "mozilla\\", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "localhost", + fixedURI: "http://localhost/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "http", + fixedURI: "http://http/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "https", + fixedURI: "http://https/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "localhost:8080", + fixedURI: "http://localhost:8080/", + protocolChange: true, + }, + { + input: "plonk:8080", + fixedURI: "http://plonk:8080/", + protocolChange: true, + }, + { + input: "plonk:8080?test", + fixedURI: "http://plonk:8080/?test", + protocolChange: true, + }, + { + input: "plonk:8080#test", + fixedURI: "http://plonk:8080/#test", + protocolChange: true, + }, + { + input: "plonk/ #", + fixedURI: "http://plonk/%20#", + alternateURI: "https://www.plonk.com/%20#", + protocolChange: true, + keywordLookup: false, + }, + { + input: "blah.com.", + fixedURI: "http://blah.com./", + protocolChange: true, + }, + { + input: + "\u10E0\u10D4\u10D2\u10D8\u10E1\u10E2\u10E0\u10D0\u10EA\u10D8\u10D0.\u10D2\u10D4", + fixedURI: "http://xn--lodaehvb5cdik4g.xn--node/", + alternateURI: "https://www.xn--lodaehvb5cdik4g.xn--node/", + protocolChange: true, + }, + { + input: " \t mozilla.org/\t \t ", + fixedURI: "http://mozilla.org/", + alternateURI: "https://www.mozilla.org/", + protocolChange: true, + }, + { + input: " moz\ti\tlla.org ", + keywordLookup: true, + }, + { + input: "mozilla/", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + protocolChange: true, + }, + { + input: "mozilla/ test /", + fixedURI: "http://mozilla/%20test%20/", + alternateURI: "https://www.mozilla.com/%20test%20/", + protocolChange: true, + }, + { + input: "mozilla /test/", + keywordLookup: true, + }, + { + input: "pserver:8080", + fixedURI: "http://pserver:8080/", + protocolChange: true, + }, + { + input: "http;mozilla", + fixedURI: "http://http;mozilla/", + alternateURI: "https://www.http;mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }, + { + input: "http//mozilla.org", + fixedURI: "http://mozilla.org/", + shouldRunTest: flags => + flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS, + }, + { + input: "http//mozilla.org", + fixedURI: "http://http//mozilla.org", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + shouldRunTest: flags => + !(flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS), + }, + { + input: "www.mozilla", + fixedURI: "http://www.mozilla/", + protocolChange: true, + }, + { + input: "https://sub.www..mozilla...org/", + fixedURI: "https://sub.www.mozilla.org/", + }, + { + input: "sub.www..mozilla...org/", + fixedURI: "http://sub.www.mozilla.org/", + protocolChange: true, + }, + { + input: "sub.www..mozilla...org", + fixedURI: "http://sub.www.mozilla.org/", + protocolChange: true, + }, + { + input: "www...mozilla", + fixedURI: "http://www.mozilla/", + keywordLookup: true, + protocolChange: true, + shouldRunTest: flags => + !gSingleWordDNSLookup && + flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + }, + { + input: "www...mozilla", + fixedURI: "http://www.mozilla/", + protocolChange: true, + shouldRunTest: flags => + gSingleWordDNSLookup || + !(flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP), + }, + { + input: "mozilla...org", + fixedURI: "http://mozilla.org/", + protocolChange: true, + }, + { + input: "モジラ...org", + fixedURI: "http://xn--yck6dwa.org/", + protocolChange: true, + }, + { + input: "user@localhost", + fixedURI: "http://user@localhost/", + protocolChange: true, + shouldRunTest: () => gSingleWordDNSLookup, + }, + { + input: "user@localhost", + fixedURI: "http://user@localhost/", + keywordLookup: true, + protocolChange: true, + shouldRunTest: () => !gSingleWordDNSLookup, + }, + { + input: "user@192.168.0.1", + fixedURI: "http://user@192.168.0.1/", + protocolChange: true, + }, + { + input: "user@dummy-host", + fixedURI: "http://user@dummy-host/", + protocolChange: true, + shouldRunTest: () => gSingleWordDNSLookup, + }, + { + input: "user@dummy-host", + fixedURI: "http://user@dummy-host/", + keywordLookup: true, + protocolChange: true, + shouldRunTest: () => !gSingleWordDNSLookup, + }, + { + input: "user:pass@dummy-host", + fixedURI: "http://user:pass@dummy-host/", + protocolChange: true, + }, + { + input: ":pass@dummy-host", + fixedURI: "http://:pass@dummy-host/", + protocolChange: true, + }, + { + input: "user@dummy-host/path", + fixedURI: "http://user@dummy-host/path", + protocolChange: true, + shouldRunTest: () => gSingleWordDNSLookup, + }, + { + input: "jar:file:///omni.ja!/", + fixedURI: "jar:file:///omni.ja!/", + }, +]; + +if (AppConstants.platform == "win") { + testcases.push({ + input: "C:\\some\\file.txt", + fixedURI: "file:///C:/some/file.txt", + protocolChange: true, + }); + testcases.push({ + input: "//mozilla", + fixedURI: "http://mozilla/", + alternateURI: "https://www.mozilla.com/", + protocolChange: true, + }); + testcases.push({ + input: "/a", + fixedURI: "http://a/", + alternateURI: "https://www.a.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleWordHosts: true, + }); +} else { + testcases.push({ + input: "/some/file.txt", + fixedURI: "file:///some/file.txt", + protocolChange: true, + }); + testcases.push({ + input: "//mozilla", + fixedURI: "file:////mozilla", + protocolChange: true, + }); + testcases.push({ + input: "/a", + fixedURI: "file:///a", + protocolChange: true, + }); +} + +function sanitize(input) { + return input.replace(/\r|\n/g, "").trim(); +} + +add_task(async function setup() { + var prefList = [ + "browser.fixup.typo.scheme", + "keyword.enabled", + "browser.fixup.domainwhitelist.whitelisted", + "browser.fixup.domainsuffixwhitelist.test", + "browser.fixup.domainsuffixwhitelist.local.domain", + "browser.search.separatePrivateDefault", + "browser.search.separatePrivateDefault.ui.enabled", + ]; + for (let pref of prefList) { + Services.prefs.setBoolPref(pref, true); + } + + await setupSearchService(); + await addTestEngines(); + + await Services.search.setDefault( + Services.search.getEngineByName(kSearchEngineID), + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + await Services.search.setDefaultPrivate( + Services.search.getEngineByName(kPrivateSearchEngineID), + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); +}); + +var gSingleWordDNSLookup = false; +add_task(async function run_test() { + // Only keywordlookup things should be affected by requiring a DNS lookup for single-word hosts: + info( + "Check only keyword lookup testcases should be affected by requiring DNS for single hosts" + ); + let affectedTests = testcases.filter( + t => !t.keywordLookup && t.affectedByDNSForSingleWordHosts + ); + if (affectedTests.length) { + for (let testcase of affectedTests) { + info("Affected: " + testcase.input); + } + } + Assert.equal(affectedTests.length, 0); + await do_single_test_run(); + await do_single_test_run(true); + gSingleWordDNSLookup = true; + await do_single_test_run(); + await do_single_test_run(true); + gSingleWordDNSLookup = false; + await Services.search.setDefault( + Services.search.getEngineByName(kPostSearchEngineID), + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + await do_single_test_run(); + await do_single_test_run(true); +}); + +async function do_single_test_run(browserFixupAlternateEnabled = false) { + Services.prefs.setBoolPref(kForceDNSLookup, gSingleWordDNSLookup); + Services.prefs.setBoolPref(kFixupEnabled, browserFixupAlternateEnabled); + let relevantTests = gSingleWordDNSLookup + ? testcases.filter(t => t.keywordLookup) + : testcases; + + let engine = await Services.search.getDefault(); + let engineUrl = + engine.name == kPostSearchEngineID + ? kPostSearchEngineURL + : kSearchEngineURL; + let privateEngine = await Services.search.getDefaultPrivate(); + let privateEngineUrl = kPrivateSearchEngineURL; + + for (let { + input: testInput, + fixedURI: expectedFixedURI, + alternateURI: alternativeURI, + keywordLookup: expectKeywordLookup, + protocolChange: expectProtocolChange, + inWhitelist: inWhitelist, + affectedByDNSForSingleWordHosts: affectedByDNSForSingleWordHosts, + shouldRunTest, + } of relevantTests) { + // Explicitly force these into a boolean + expectKeywordLookup = !!expectKeywordLookup; + expectProtocolChange = !!expectProtocolChange; + inWhitelist = !!inWhitelist; + affectedByDNSForSingleWordHosts = !!affectedByDNSForSingleWordHosts; + + expectKeywordLookup = + expectKeywordLookup && + (!affectedByDNSForSingleWordHosts || !gSingleWordDNSLookup); + + for (let flags of flagInputs) { + info( + 'Checking "' + + testInput + + '" with flags ' + + flags + + " (DNS lookup for single words: " + + (gSingleWordDNSLookup ? "yes" : "no") + + "," + + " browser fixup alt enabled: " + + (browserFixupAlternateEnabled ? "yes" : "no") + + ")" + ); + + if (shouldRunTest && !shouldRunTest(flags)) { + continue; + } + + let URIInfo; + try { + URIInfo = Services.uriFixup.getFixupURIInfo(testInput, flags); + } catch (ex) { + // Both APIs should return an error in the same cases. + info("Caught exception: " + ex); + Assert.equal(expectedFixedURI, null); + continue; + } + + // Check the fixedURI: + let fixupFlag = browserFixupAlternateEnabled + ? Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI + : Services.uriFixup.FIXUP_FLAG_NONE; + let forceAlternativeURI = + flags & Services.uriFixup.FIXUP_FLAG_FORCE_ALTERNATE_URI; + let makeAlternativeURI = flags & fixupFlag || forceAlternativeURI; + + if (makeAlternativeURI && alternativeURI != null) { + Assert.equal( + URIInfo.fixedURI.spec, + alternativeURI, + "should have gotten alternate URI" + ); + } else { + Assert.equal( + URIInfo.fixedURI && URIInfo.fixedURI.spec, + expectedFixedURI, + "should get correct fixed URI" + ); + } + + // Check booleans on input: + let couldDoKeywordLookup = + flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + Assert.equal( + !!URIInfo.keywordProviderName, + couldDoKeywordLookup && expectKeywordLookup, + "keyword lookup as expected" + ); + + let expectProtocolChangeAfterAlternate = false; + // If alternativeURI was created, the protocol of the URI + // might have been changed to browser.fixup.alternate.protocol + // If the protocol is not the same as what was in expectedFixedURI, + // the protocol must've changed in the fixup process. + if ( + makeAlternativeURI && + alternativeURI != null && + !expectedFixedURI.startsWith(URIInfo.fixedURI.scheme) + ) { + expectProtocolChangeAfterAlternate = true; + } + + Assert.equal( + URIInfo.fixupChangedProtocol, + expectProtocolChange || expectProtocolChangeAfterAlternate, + "protocol change as expected" + ); + Assert.equal( + URIInfo.fixupCreatedAlternateURI, + makeAlternativeURI && alternativeURI != null, + "alternative URI as expected" + ); + + // Check the preferred URI + if (couldDoKeywordLookup) { + if (expectKeywordLookup) { + if (!inWhitelist) { + let urlparamInput = encodeURIComponent(sanitize(testInput)).replace( + /%20/g, + "+" + ); + // If the input starts with `?`, then URIInfo.preferredURI.spec will omit it + // In order to test this behaviour, remove `?` only if it is the first character + if (urlparamInput.startsWith("%3F")) { + urlparamInput = urlparamInput.replace("%3F", ""); + } + let isPrivate = + flags & Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT; + let searchEngineUrl = isPrivate ? privateEngineUrl : engineUrl; + let searchURL = searchEngineUrl.replace( + "{searchTerms}", + urlparamInput + ); + let spec = URIInfo.preferredURI.spec.replace(/%27/g, "'"); + Assert.equal(spec, searchURL, "should get correct search URI"); + let providerName = isPrivate ? privateEngine.name : engine.name; + Assert.equal( + URIInfo.keywordProviderName, + providerName, + "should get correct provider name" + ); + // Also check keywordToURI() uses the right engine. + let kwInfo = Services.uriFixup.keywordToURI( + urlparamInput, + isPrivate + ); + Assert.equal(kwInfo.providerName, URIInfo.providerName); + if (providerName == kPostSearchEngineID) { + Assert.ok(kwInfo.postData); + let submission = engine.getSubmission(urlparamInput); + let enginePostData = NetUtil.readInputStreamToString( + submission.postData, + submission.postData.available() + ); + let postData = NetUtil.readInputStreamToString( + kwInfo.postData, + kwInfo.postData.available() + ); + Assert.equal(postData, enginePostData); + } + } else { + Assert.equal( + URIInfo.preferredURI, + null, + "not expecting a preferred URI" + ); + } + } else { + Assert.equal( + URIInfo.preferredURI.spec, + URIInfo.fixedURI.spec, + "fixed URI should match" + ); + } + } else { + // In these cases, we should never be doing a keyword lookup and + // the fixed URI should be preferred: + let prefURI = URIInfo.preferredURI && URIInfo.preferredURI.spec; + let fixedURI = URIInfo.fixedURI && URIInfo.fixedURI.spec; + Assert.equal(prefURI, fixedURI, "fixed URI should be same as expected"); + } + Assert.equal( + sanitize(testInput), + URIInfo.originalInput, + "should mirror original input" + ); + } + } +} diff --git a/docshell/test/unit/test_URIFixup_search.js b/docshell/test/unit/test_URIFixup_search.js new file mode 100644 index 0000000000..fa1c5b38ba --- /dev/null +++ b/docshell/test/unit/test_URIFixup_search.js @@ -0,0 +1,143 @@ +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +var isWin = AppConstants.platform == "win"; + +var data = [ + { + // Valid should not be changed. + wrong: "https://example.com/this/is/a/test.html", + fixed: "https://example.com/this/is/a/test.html", + }, + { + // Unrecognized protocols should be changed. + wrong: "whatever://this/is/a/test.html", + fixed: kSearchEngineURL.replace( + "{searchTerms}", + encodeURIComponent("whatever://this/is/a/test.html") + ), + }, + + { + // Unrecognized protocols should be changed. + wrong: "whatever://this/is/a/test.html", + fixed: kPrivateSearchEngineURL.replace( + "{searchTerms}", + encodeURIComponent("whatever://this/is/a/test.html") + ), + inPrivateBrowsing: true, + }, + + // The following tests check that when a user:password is present in the URL + // `user:` isn't treated as an unknown protocol thus leaking the user and + // password to the search engine. + { + wrong: "user:pass@example.com/this/is/a/test.html", + fixed: "http://user:pass@example.com/this/is/a/test.html", + }, + { + wrong: "user@example.com:8080/this/is/a/test.html", + fixed: "http://user@example.com:8080/this/is/a/test.html", + }, + { + wrong: "https:pass@example.com/this/is/a/test.html", + fixed: "https://pass@example.com/this/is/a/test.html", + }, + { + wrong: "user:pass@example.com:8080/this/is/a/test.html", + fixed: "http://user:pass@example.com:8080/this/is/a/test.html", + }, + { + wrong: "http:user:pass@example.com:8080/this/is/a/test.html", + fixed: "http://user:pass@example.com:8080/this/is/a/test.html", + }, + { + wrong: "ttp:user:pass@example.com:8080/this/is/a/test.html", + fixed: "http://user:pass@example.com:8080/this/is/a/test.html", + }, + { + wrong: "nonsense:user:pass@example.com:8080/this/is/a/test.html", + fixed: "http://nonsense:user%3Apass@example.com:8080/this/is/a/test.html", + }, + { + wrong: "user:@example.com:8080/this/is/a/test.html", + fixed: "http://user@example.com:8080/this/is/a/test.html", + }, + { + wrong: "//user:pass@example.com:8080/this/is/a/test.html", + fixed: + (isWin ? "http:" : "file://") + + "//user:pass@example.com:8080/this/is/a/test.html", + }, + { + wrong: "://user:pass@example.com:8080/this/is/a/test.html", + fixed: "http://user:pass@example.com:8080/this/is/a/test.html", + }, + { + wrong: "localhost:8080/?param=1", + fixed: "http://localhost:8080/?param=1", + }, + { + wrong: "localhost:8080?param=1", + fixed: "http://localhost:8080/?param=1", + }, + { + wrong: "localhost:8080#somewhere", + fixed: "http://localhost:8080/#somewhere", + }, + { + wrong: "whatever://this/is/a@b/test.html", + fixed: kSearchEngineURL.replace( + "{searchTerms}", + encodeURIComponent("whatever://this/is/a@b/test.html") + ), + }, +]; + +var extProtocolSvc = Cc[ + "@mozilla.org/uriloader/external-protocol-service;1" +].getService(Ci.nsIExternalProtocolService); + +if (extProtocolSvc && extProtocolSvc.externalProtocolHandlerExists("mailto")) { + data.push({ + wrong: "mailto:foo@bar.com", + fixed: "mailto:foo@bar.com", + }); +} + +var len = data.length; + +add_task(async function setup() { + await setupSearchService(); + await addTestEngines(); + + Services.prefs.setBoolPref("keyword.enabled", true); + Services.prefs.setBoolPref("browser.search.separatePrivateDefault", true); + Services.prefs.setBoolPref( + "browser.search.separatePrivateDefault.ui.enabled", + true + ); + + await Services.search.setDefault( + Services.search.getEngineByName(kSearchEngineID), + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + await Services.search.setDefaultPrivate( + Services.search.getEngineByName(kPrivateSearchEngineID), + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); +}); + +// Make sure we fix what needs fixing +add_task(function test_fix_unknown_schemes() { + for (let i = 0; i < len; ++i) { + let item = data[i]; + let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS; + if (item.inPrivateBrowsing) { + flags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT; + } + let { preferredURI } = Services.uriFixup.getFixupURIInfo(item.wrong, flags); + Assert.equal(preferredURI.spec, item.fixed); + } +}); diff --git a/docshell/test/unit/test_allowJavascript.js b/docshell/test/unit/test_allowJavascript.js new file mode 100644 index 0000000000..d1341c7bba --- /dev/null +++ b/docshell/test/unit/test_allowJavascript.js @@ -0,0 +1,291 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const ACTOR = "AllowJavascript"; + +const HTML = String.raw` + + + + + + + +`; + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com", "example.org"], +}); + +server.registerPathHandler("/", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(HTML); +}); + +const { AllowJavascriptParent } = ChromeUtils.importESModule( + "resource://test/AllowJavascriptParent.sys.mjs" +); + +async function assertScriptsAllowed(bc, expectAllowed, desc) { + let actor = bc.currentWindowGlobal.getActor(ACTOR); + let allowed = await actor.sendQuery("CheckScriptsAllowed"); + equal( + allowed, + expectAllowed, + `Scripts should be ${expectAllowed ? "" : "dis"}allowed for ${desc}` + ); +} + +async function assertLoadFired(bc, expectFired, desc) { + let actor = bc.currentWindowGlobal.getActor(ACTOR); + let fired = await actor.sendQuery("CheckFiredLoadEvent"); + equal( + fired, + expectFired, + `Should ${expectFired ? "" : "not "}have fired load for ${desc}` + ); +} + +function createSubframe(bc, url) { + let actor = bc.currentWindowGlobal.getActor(ACTOR); + return actor.sendQuery("CreateIframe", { url }); +} + +add_task(async function () { + Services.prefs.setBoolPref("dom.security.https_first", false); + ChromeUtils.registerWindowActor(ACTOR, { + allFrames: true, + child: { + esModuleURI: "resource://test/AllowJavascriptChild.sys.mjs", + events: { load: { capture: true } }, + }, + parent: { + esModuleURI: "resource://test/AllowJavascriptParent.sys.mjs", + }, + }); + + let page = await XPCShellContentUtils.loadContentPage("http://example.com/", { + remote: true, + remoteSubframes: true, + }); + + let bc = page.browsingContext; + + { + let oopFrame1 = await createSubframe(bc, "http://example.org/"); + let inprocFrame1 = await createSubframe(bc, "http://example.com/"); + + let oopFrame1OopSub = await createSubframe( + oopFrame1, + "http://example.com/" + ); + let inprocFrame1OopSub = await createSubframe( + inprocFrame1, + "http://example.org/" + ); + + equal( + oopFrame1.allowJavascript, + true, + "OOP BC should inherit allowJavascript from parent" + ); + equal( + inprocFrame1.allowJavascript, + true, + "In-process BC should inherit allowJavascript from parent" + ); + equal( + oopFrame1OopSub.allowJavascript, + true, + "OOP BC child should inherit allowJavascript from parent" + ); + equal( + inprocFrame1OopSub.allowJavascript, + true, + "In-process child BC should inherit allowJavascript from parent" + ); + + await assertLoadFired(bc, true, "top BC"); + await assertScriptsAllowed(bc, true, "top BC"); + + await assertLoadFired(oopFrame1, true, "OOP frame 1"); + await assertScriptsAllowed(oopFrame1, true, "OOP frame 1"); + + await assertLoadFired(inprocFrame1, true, "In-process frame 1"); + await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1"); + + await assertLoadFired(oopFrame1OopSub, true, "OOP frame 1 subframe"); + await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe"); + + await assertLoadFired( + inprocFrame1OopSub, + true, + "In-process frame 1 subframe" + ); + await assertScriptsAllowed( + inprocFrame1OopSub, + true, + "In-process frame 1 subframe" + ); + + bc.allowJavascript = false; + await assertScriptsAllowed(bc, false, "top BC with scripts disallowed"); + await assertScriptsAllowed( + oopFrame1, + false, + "OOP frame 1 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1, + false, + "In-process frame 1 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + oopFrame1OopSub, + false, + "OOP frame 1 subframe with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1OopSub, + false, + "In-process frame 1 subframe with top BC with scripts disallowed" + ); + + let oopFrame2 = await createSubframe(bc, "http://example.org/"); + let inprocFrame2 = await createSubframe(bc, "http://example.com/"); + + equal( + oopFrame2.allowJavascript, + false, + "OOP BC 2 should inherit allowJavascript from parent" + ); + equal( + inprocFrame2.allowJavascript, + false, + "In-process BC 2 should inherit allowJavascript from parent" + ); + + await assertLoadFired( + oopFrame2, + undefined, + "OOP frame 2 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + oopFrame2, + false, + "OOP frame 2 with top BC with scripts disallowed" + ); + await assertLoadFired( + inprocFrame2, + undefined, + "In-process frame 2 with top BC with scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame2, + false, + "In-process frame 2 with top BC with scripts disallowed" + ); + + bc.allowJavascript = true; + await assertScriptsAllowed(bc, true, "top BC"); + + await assertScriptsAllowed(oopFrame1, true, "OOP frame 1"); + await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1"); + await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe"); + await assertScriptsAllowed( + inprocFrame1OopSub, + true, + "In-process frame 1 subframe" + ); + + await assertScriptsAllowed(oopFrame2, false, "OOP frame 2"); + await assertScriptsAllowed(inprocFrame2, false, "In-process frame 2"); + + oopFrame1.currentWindowGlobal.allowJavascript = false; + inprocFrame1.currentWindowGlobal.allowJavascript = false; + + await assertScriptsAllowed( + oopFrame1, + false, + "OOP frame 1 with second level WC scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1, + false, + "In-process frame 1 with second level WC scripts disallowed" + ); + await assertScriptsAllowed( + oopFrame1OopSub, + false, + "OOP frame 1 subframe second level WC scripts disallowed" + ); + await assertScriptsAllowed( + inprocFrame1OopSub, + false, + "In-process frame 1 subframe with second level WC scripts disallowed" + ); + + oopFrame1.reload(0); + inprocFrame1.reload(0); + await Promise.all([ + AllowJavascriptParent.promiseLoad(oopFrame1), + AllowJavascriptParent.promiseLoad(inprocFrame1), + ]); + + equal( + oopFrame1.currentWindowGlobal.allowJavascript, + true, + "WindowContext.allowJavascript does not persist after navigation for OOP frame 1" + ); + equal( + inprocFrame1.currentWindowGlobal.allowJavascript, + true, + "WindowContext.allowJavascript does not persist after navigation for in-process frame 1" + ); + + await assertScriptsAllowed(oopFrame1, true, "OOP frame 1"); + await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1"); + } + + bc.allowJavascript = false; + + bc.reload(0); + await AllowJavascriptParent.promiseLoad(bc); + + await assertLoadFired( + bc, + undefined, + "top BC with scripts disabled after reload" + ); + await assertScriptsAllowed( + bc, + false, + "top BC with scripts disabled after reload" + ); + + await page.loadURL("http://example.org/?other"); + bc = page.browsingContext; + + await assertLoadFired( + bc, + undefined, + "top BC with scripts disabled after navigation" + ); + await assertScriptsAllowed( + bc, + false, + "top BC with scripts disabled after navigation" + ); + + await page.close(); + Services.prefs.clearUserPref("dom.security.https_first"); +}); diff --git a/docshell/test/unit/test_browsing_context_structured_clone.js b/docshell/test/unit/test_browsing_context_structured_clone.js new file mode 100644 index 0000000000..1c91a9b0b2 --- /dev/null +++ b/docshell/test/unit/test_browsing_context_structured_clone.js @@ -0,0 +1,70 @@ +"use strict"; + +add_task(async function test_BrowsingContext_structured_clone() { + let browser = Services.appShell.createWindowlessBrowser(false); + + let frame = browser.document.createElement("iframe"); + + await new Promise(r => { + frame.onload = () => r(); + browser.document.body.appendChild(frame); + }); + + let { browsingContext } = frame; + + let sch = new StructuredCloneHolder("debug name", " debug name", { + browsingContext, + }); + + let deserialize = () => sch.deserialize({}, true); + + // Check that decoding a live browsing context produces the correct + // object. + equal( + deserialize().browsingContext, + browsingContext, + "Got correct browsing context from StructuredClone deserialize" + ); + + // Check that decoding a second time still succeeds. + equal( + deserialize().browsingContext, + browsingContext, + "Got correct browsing context from second StructuredClone deserialize" + ); + + // Destroy the browsing context and make sure that the decode fails + // with a DataCloneError. + // + // Making sure the BrowsingContext is actually destroyed by the time + // we do the second decode is a bit tricky. We obviously have clear + // our local references to it, and give the GC a chance to reap them. + // And we also, of course, have to destroy the frame that it belongs + // to, or its frame loader and window global would hold it alive. + // + // Beyond that, we don't *have* to reload or destroy the parent + // document, but we do anyway just to be safe. + // + + frame.remove(); + frame = null; + browsingContext = null; + + browser.document.location.reload(); + browser.close(); + + // We will schedule a precise GC and do both GC and CC a few times, to make + // sure we have completely destroyed the WindowGlobal actors (which keep + // references to their BrowsingContexts) in order + // to allow their (now snow-white) references to be collected. + await schedulePreciseGCAndForceCC(3); + + // OK. We can be fairly confident that the BrowsingContext object + // stored in our structured clone data has been destroyed. Make sure + // that attempting to decode it again leads to the appropriate error. + Assert.throws( + deserialize, + e => e.name === "DataCloneError", + "Should get a DataCloneError when trying to decode a dead BrowsingContext" + ); +}); diff --git a/docshell/test/unit/test_bug442584.js b/docshell/test/unit/test_bug442584.js new file mode 100644 index 0000000000..c109557f50 --- /dev/null +++ b/docshell/test/unit/test_bug442584.js @@ -0,0 +1,35 @@ +var prefetch = Cc["@mozilla.org/prefetch-service;1"].getService( + Ci.nsIPrefetchService +); + +var ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" +); + +function run_test() { + // Fill up the queue + Services.prefs.setBoolPref("network.prefetch-next", true); + for (var i = 0; i < 5; i++) { + var uri = Services.io.newURI("http://localhost/" + i); + var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri); + prefetch.prefetchURI(uri, referrerInfo, null, true); + } + + // Make sure the queue has items in it... + Assert.ok(prefetch.hasMoreElements()); + + // Now disable the pref to force the queue to empty... + Services.prefs.setBoolPref("network.prefetch-next", false); + Assert.ok(!prefetch.hasMoreElements()); + + // Now reenable the pref, and add more items to the queue. + Services.prefs.setBoolPref("network.prefetch-next", true); + for (var k = 0; k < 5; k++) { + var uri2 = Services.io.newURI("http://localhost/" + k); + var referrerInfo2 = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri2); + prefetch.prefetchURI(uri2, referrerInfo2, null, true); + } + Assert.ok(prefetch.hasMoreElements()); +} diff --git a/docshell/test/unit/test_pb_notification.js b/docshell/test/unit/test_pb_notification.js new file mode 100644 index 0000000000..51cd3b95ff --- /dev/null +++ b/docshell/test/unit/test_pb_notification.js @@ -0,0 +1,18 @@ +function destroy_transient_docshell() { + let windowlessBrowser = Services.appShell.createWindowlessBrowser(true); + windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 1 }); + windowlessBrowser.close(); + do_test_pending(); + do_timeout(0, Cu.forceGC); +} + +function run_test() { + var obs = { + observe(aSubject, aTopic, aData) { + Assert.equal(aTopic, "last-pb-context-exited"); + do_test_finished(); + }, + }; + Services.obs.addObserver(obs, "last-pb-context-exited"); + destroy_transient_docshell(); +} diff --git a/docshell/test/unit/test_privacy_transition.js b/docshell/test/unit/test_privacy_transition.js new file mode 100644 index 0000000000..ae1bf71284 --- /dev/null +++ b/docshell/test/unit/test_privacy_transition.js @@ -0,0 +1,21 @@ +var gNotifications = 0; + +var observer = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPrivacyTransitionObserver", + "nsISupportsWeakReference", + ]), + + privateModeChanged(enabled) { + gNotifications++; + }, +}; + +function run_test() { + let windowlessBrowser = Services.appShell.createWindowlessBrowser(true); + windowlessBrowser.docShell.addWeakPrivacyTransitionObserver(observer); + windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 1 }); + windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 0 }); + windowlessBrowser.close(); + Assert.equal(gNotifications, 2); +} diff --git a/docshell/test/unit/test_subframe_stop_after_parent_error.js b/docshell/test/unit/test_subframe_stop_after_parent_error.js new file mode 100644 index 0000000000..2f80d18ebb --- /dev/null +++ b/docshell/test/unit/test_subframe_stop_after_parent_error.js @@ -0,0 +1,146 @@ +"use strict"; +// Tests that pending subframe requests for an initial about:blank +// document do not delay showing load errors (and possibly result in a +// crash at docShell destruction) for failed document loads. + +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/); + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com"], +}); + +// Registers a URL with the HTTP server which will not return a response +// until we're ready to. +function registerSlowPage(path) { + let result = { + url: `http://example.com/${path}`, + }; + + let finishedPromise = new Promise(resolve => { + result.finish = resolve; + }); + + server.registerPathHandler(`/${path}`, async (request, response) => { + response.processAsync(); + + response.setHeader("Content-Type", "text/html"); + response.write("Hello."); + + await finishedPromise; + + response.finish(); + }); + + return result; +} + +let topFrameRequest = registerSlowPage("top.html"); +let subFrameRequest = registerSlowPage("frame.html"); + +let thunks = new Set(); +function promiseStateStop(webProgress) { + return new Promise(resolve => { + let listener = { + onStateChange(aWebProgress, request, stateFlags, status) { + if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + webProgress.removeProgressListener(listener); + + thunks.delete(listener); + resolve(); + } + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), + }; + + // Keep the listener alive, since it's stored as a weak reference. + thunks.add(listener); + webProgress.addProgressListener( + listener, + Ci.nsIWebProgress.NOTIFY_STATE_REQUEST + ); + }); +} + +async function runTest(waitForErrorPage) { + let page = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: false, + }); + let { browser } = page; + + // Watch for the HTTP request for the top frame so that we can cancel + // it with an error. + let requestPromise = TestUtils.topicObserved( + "http-on-modify-request", + subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url + ); + + // Create a frame with a source URL which will not return a response + // before we cancel it with an error. + let doc = browser.contentDocument; + let frame = doc.createElement("iframe"); + frame.src = topFrameRequest.url; + doc.body.appendChild(frame); + + // Create a subframe in the initial about:blank document for the above + // frame which will not return a response before we cancel the + // document request. + let frameDoc = frame.contentDocument; + let subframe = frameDoc.createElement("iframe"); + subframe.src = subFrameRequest.url; + frameDoc.body.appendChild(subframe); + + let [req] = await requestPromise; + + info("Cancel request for parent frame"); + req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED); + + // Request cancelation is not synchronous, so wait for the STATE_STOP + // event to fire. + await promiseStateStop( + browser.docShell.nsIInterfaceRequestor.getInterface(Ci.nsIWebProgress) + ); + + // And make a trip through the event loop to give the DocLoader a + // chance to update its state. + await new Promise(executeSoon); + + if (waitForErrorPage) { + // Make sure that canceling the request with an error code actually + // shows an error page without waiting for a subframe response. + await TestUtils.waitForCondition(() => + frame.contentDocument.documentURI.startsWith("about:neterror?") + ); + } + + info("Remove frame"); + frame.remove(); + + await page.close(); +} + +add_task(async function testRemoveFrameImmediately() { + await runTest(false); +}); + +add_task(async function testRemoveFrameAfterErrorPage() { + await runTest(true); +}); + +add_task(async function () { + // Allow the document requests for the frames to complete. + topFrameRequest.finish(); + subFrameRequest.finish(); +}); diff --git a/docshell/test/unit/xpcshell.ini b/docshell/test/unit/xpcshell.ini new file mode 100644 index 0000000000..8bd7ef4f28 --- /dev/null +++ b/docshell/test/unit/xpcshell.ini @@ -0,0 +1,35 @@ +[DEFAULT] +head = head_docshell.js +support-files = + data/engine.xml + data/enginePost.xml + data/enginePrivate.xml + +[test_allowJavascript.js] +skip-if = os == 'android' +support-files = + AllowJavascriptChild.sys.mjs + AllowJavascriptParent.sys.mjs +[test_bug442584.js] +[test_browsing_context_structured_clone.js] +[test_URIFixup.js] +[test_URIFixup_check_host.js] +[test_URIFixup_external_protocol_fallback.js] +skip-if = os == 'android' +[test_URIFixup_forced.js] +# Disabled for 1563343 -- URI fixup should be done at the app level in GV. +skip-if = os == 'android' +[test_URIFixup_search.js] +skip-if = os == 'android' +[test_URIFixup_info.js] +skip-if = + os == 'android' + win10_2004 && debug # Bug 1727925 +[test_pb_notification.js] +# Bug 751575: unrelated JS changes cause timeouts on random platforms +skip-if = true +[test_privacy_transition.js] +[test_subframe_stop_after_parent_error.js] +skip-if = + os == 'android' + appname == 'thunderbird' # Needs to run without E10s, can't do that. diff --git a/docshell/test/unit_ipc/test_pb_notification_ipc.js b/docshell/test/unit_ipc/test_pb_notification_ipc.js new file mode 100644 index 0000000000..6408e7753e --- /dev/null +++ b/docshell/test/unit_ipc/test_pb_notification_ipc.js @@ -0,0 +1,15 @@ +function run_test() { + var notifications = 0; + var obs = { + observe(aSubject, aTopic, aData) { + Assert.equal(aTopic, "last-pb-context-exited"); + notifications++; + }, + }; + Services.obs.addObserver(obs, "last-pb-context-exited"); + + run_test_in_child("../unit/test_pb_notification.js", function () { + Assert.equal(notifications, 1); + do_test_finished(); + }); +} diff --git a/docshell/test/unit_ipc/xpcshell.ini b/docshell/test/unit_ipc/xpcshell.ini new file mode 100644 index 0000000000..30e98a270f --- /dev/null +++ b/docshell/test/unit_ipc/xpcshell.ini @@ -0,0 +1,7 @@ +[DEFAULT] +head = +skip-if = toolkit == 'android' + +[test_pb_notification_ipc.js] +# Bug 751575: Perma-fails with: command timed out: 1200 seconds without output +skip-if = true -- cgit v1.2.3